moov box writing support, minor stream box refactor
diff --git a/src/read.c b/src/read.c
index 7798d31..17e5d5d 100644
--- a/src/read.c
+++ b/src/read.c
@@ -727,8 +727,7 @@
     BEGIN_STREAM(s, raw, rawLen);
 
     uint8_t version;
-    uint8_t flags[3];
-    CHECK(avifROStreamReadVersionAndFlags(&s, &version, flags));
+    CHECK(avifROStreamReadVersionAndFlags(&s, &version, NULL));
     if (version > 2) {
         return AVIF_FALSE;
     }
@@ -1054,9 +1053,9 @@
     BEGIN_STREAM(s, raw, rawLen);
 
     uint8_t version;
-    uint8_t flags[3];
-    CHECK(avifROStreamReadVersionAndFlags(&s, &version, flags));
-    avifBool propertyIndexIsU16 = ((flags[2] & 0x1) != 0);
+    uint32_t flags;
+    CHECK(avifROStreamReadVersionAndFlags(&s, &version, &flags));
+    avifBool propertyIndexIsU16 = ((flags & 0x1) != 0);
 
     uint32_t entryCount;
     CHECK(avifROStreamReadU32(&s, &entryCount));
@@ -1387,8 +1386,7 @@
     (void)data;
 
     uint8_t version;
-    uint8_t flags[3];
-    CHECK(avifROStreamReadVersionAndFlags(&s, &version, flags));
+    CHECK(avifROStreamReadVersionAndFlags(&s, &version, NULL));
 
     uint32_t ignored32, trackID;
     uint64_t ignored64;
@@ -1437,8 +1435,7 @@
     (void)data;
 
     uint8_t version;
-    uint8_t flags[3];
-    CHECK(avifROStreamReadVersionAndFlags(&s, &version, flags));
+    CHECK(avifROStreamReadVersionAndFlags(&s, &version, NULL));
 
     uint32_t ignored32, mediaTimescale, mediaDuration32;
     uint64_t ignored64, mediaDuration64;
@@ -1984,6 +1981,9 @@
             if (!track->sampleTable) {
                 continue;
             }
+            if (!track->id) {
+                continue;
+            }
             if (!track->sampleTable->chunks.count) {
                 continue;
             }
@@ -2008,6 +2008,9 @@
             if (!track->sampleTable) {
                 continue;
             }
+            if (!track->id) {
+                continue;
+            }
             if (!track->sampleTable->chunks.count) {
                 continue;
             }
diff --git a/src/stream.c b/src/stream.c
index 0acafe9..e882aed 100644
--- a/src/stream.c
+++ b/src/stream.c
@@ -168,7 +168,7 @@
     return AVIF_TRUE;
 }
 
-avifBool avifROStreamReadVersionAndFlags(avifROStream * stream, uint8_t * version, uint8_t * flags)
+avifBool avifROStreamReadVersionAndFlags(avifROStream * stream, uint8_t * version, uint32_t * flags)
 {
     uint8_t versionAndFlags[4];
     CHECK(avifROStreamRead(stream, versionAndFlags, 4));
@@ -176,7 +176,7 @@
         *version = versionAndFlags[0];
     }
     if (flags) {
-        memcpy(flags, &versionAndFlags[1], 3);
+        *flags = (versionAndFlags[1] << 16) + (versionAndFlags[2] << 8) + (versionAndFlags[3] << 0);
     }
     return AVIF_TRUE;
 }
@@ -250,7 +250,7 @@
     avifRWStreamWrite(stream, (const uint8_t *)chars, size);
 }
 
-avifBoxMarker avifRWStreamWriteBox(avifRWStream * stream, const char * type, int version, size_t contentSize)
+avifBoxMarker avifRWStreamWriteFullBox(avifRWStream * stream, const char * type, size_t contentSize, int version, uint32_t flags)
 {
     avifBoxMarker marker = stream->offset;
     size_t headerSize = sizeof(uint32_t) + 4 /* size of type */;
@@ -262,6 +262,9 @@
     memset(stream->raw->data + stream->offset, 0, headerSize);
     if (version != -1) {
         stream->raw->data[stream->offset + 8] = (uint8_t)version;
+        stream->raw->data[stream->offset + 9] = (uint8_t)((flags >> 16) & 0xff);
+        stream->raw->data[stream->offset + 10] = (uint8_t)((flags >> 8) & 0xff);
+        stream->raw->data[stream->offset + 11] = (uint8_t)((flags >> 0) & 0xff);
     }
     uint32_t noSize = avifNTOHL((uint32_t)(headerSize + contentSize));
     memcpy(stream->raw->data + stream->offset, &noSize, sizeof(uint32_t));
@@ -271,6 +274,11 @@
     return marker;
 }
 
+avifBoxMarker avifRWStreamWriteBox(avifRWStream * stream, const char * type, size_t contentSize)
+{
+    return avifRWStreamWriteFullBox(stream, type, contentSize, -1, 0);
+}
+
 void avifRWStreamFinishBox(avifRWStream * stream, avifBoxMarker marker)
 {
     uint32_t noSize = avifNTOHL((uint32_t)(stream->offset - marker));
@@ -303,6 +311,15 @@
     stream->offset += size;
 }
 
+void avifRWStreamWriteU64(avifRWStream * stream, uint64_t v)
+{
+    size_t size = sizeof(uint64_t);
+    v = avifHTON64(v);
+    makeRoom(stream, size);
+    memcpy(stream->raw->data + stream->offset, &v, size);
+    stream->offset += size;
+}
+
 void avifRWStreamWriteZeros(avifRWStream * stream, size_t byteCount)
 {
     makeRoom(stream, byteCount);
diff --git a/src/write.c b/src/write.c
index 530a3b4..fcdd7f1 100644
--- a/src/write.c
+++ b/src/write.c
@@ -4,6 +4,7 @@
 #include "avif/internal.h"
 
 #include <string.h>
+#include <time.h>
 
 #define MAX_ASSOCIATIONS 16
 struct ipmaArray
@@ -74,6 +75,7 @@
     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
 
     uint16_t irefToID; // if non-zero, make an iref from this id -> irefToID
     const char * irefType;
@@ -87,7 +89,7 @@
 
 typedef struct avifEncoderFrame
 {
-    avifImageTiming timing;
+    uint64_t durationInTimescales;
 } avifEncoderFrame;
 AVIF_ARRAY_DECLARE(avifEncoderFrameArray, avifEncoderFrame, frame);
 
@@ -158,11 +160,7 @@
     encoder->tileColsLog2 = 0;
     encoder->speed = AVIF_SPEED_DEFAULT;
     encoder->data = avifEncoderDataCreate();
-    encoder->imageTiming.timescale = 1;
-    encoder->imageTiming.pts = 0;
-    encoder->imageTiming.ptsInTimescales = 0;
-    encoder->imageTiming.duration = 1;
-    encoder->imageTiming.durationInTimescales = 1;
+    encoder->timescale = 1;
     return encoder;
 }
 
@@ -172,7 +170,7 @@
     avifFree(encoder);
 }
 
-static avifResult avifEncoderAddImage(avifEncoder * encoder, const avifImage * image, const avifImageTiming * imageTiming)
+static avifResult avifEncoderAddImage(avifEncoder * encoder, const avifImage * image, uint64_t durationInTimescales)
 {
     // -----------------------------------------------------------------------
     // Validate image
@@ -191,8 +189,8 @@
 
     // -----------------------------------------------------------------------
 
-    if (imageTiming == NULL) {
-        imageTiming = &encoder->imageTiming;
+    if (durationInTimescales == 0) {
+        durationInTimescales = 1;
     }
 
     if (encoder->data->items.count == 0) {
@@ -296,7 +294,7 @@
     }
 
     avifEncoderFrame * frame = (avifEncoderFrame *)avifArrayPushPtr(&encoder->data->frames);
-    memcpy(&frame->timing, imageTiming, sizeof(avifImageTiming));
+    frame->durationInTimescales = durationInTimescales;
     return AVIF_RESULT_OK;
 }
 
@@ -336,6 +334,7 @@
     // Begin write stream
 
     avifImage * imageMetadata = encoder->data->imageMetadata;
+    uint64_t now = (uint64_t)time(NULL);
 
     avifRWStream s;
     avifRWStreamStart(&s, output);
@@ -343,10 +342,18 @@
     // -----------------------------------------------------------------------
     // Write ftyp
 
-    avifBoxMarker ftyp = avifRWStreamWriteBox(&s, "ftyp", -1, 0);
-    avifRWStreamWriteChars(&s, "avif", 4);                                 // unsigned int(32) major_brand;
+    const char * majorBrand = "avif";
+    if (encoder->data->frames.count > 1) {
+        majorBrand = "avis";
+    }
+
+    avifBoxMarker ftyp = avifRWStreamWriteBox(&s, "ftyp", AVIF_BOX_SIZE_TBD);
+    avifRWStreamWriteChars(&s, majorBrand, 4);                             // unsigned int(32) major_brand;
     avifRWStreamWriteU32(&s, 0);                                           // unsigned int(32) minor_version;
     avifRWStreamWriteChars(&s, "avif", 4);                                 // unsigned int(32) compatible_brands[];
+    if (encoder->data->frames.count > 1) {                                 //
+        avifRWStreamWriteChars(&s, "avis", 4);                             // ... compatible_brands[]
+    }                                                                      //
     avifRWStreamWriteChars(&s, "mif1", 4);                                 // ... compatible_brands[]
     avifRWStreamWriteChars(&s, "miaf", 4);                                 // ... compatible_brands[]
     if ((imageMetadata->depth == 8) || (imageMetadata->depth == 10)) {     //
@@ -361,12 +368,12 @@
     // -----------------------------------------------------------------------
     // Start meta
 
-    avifBoxMarker meta = avifRWStreamWriteBox(&s, "meta", 0, 0);
+    avifBoxMarker meta = avifRWStreamWriteFullBox(&s, "meta", AVIF_BOX_SIZE_TBD, 0, 0);
 
     // -----------------------------------------------------------------------
     // Write hdlr
 
-    avifBoxMarker hdlr = avifRWStreamWriteBox(&s, "hdlr", 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;
@@ -377,14 +384,14 @@
     // Write pitm
 
     if (encoder->data->primaryItemID != 0) {
-        avifRWStreamWriteBox(&s, "pitm", 0, sizeof(uint16_t));
+        avifRWStreamWriteFullBox(&s, "pitm", sizeof(uint16_t), 0, 0);
         avifRWStreamWriteU16(&s, encoder->data->primaryItemID); //  unsigned int(16) item_ID;
     }
 
     // -----------------------------------------------------------------------
     // Write iloc
 
-    avifBoxMarker iloc = avifRWStreamWriteBox(&s, "iloc", 0, 0);
+    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;
@@ -414,13 +421,13 @@
     // -----------------------------------------------------------------------
     // Write iinf
 
-    avifBoxMarker iinf = avifRWStreamWriteBox(&s, "iinf", 0, 0);
+    avifBoxMarker iinf = avifRWStreamWriteFullBox(&s, "iinf", AVIF_BOX_SIZE_TBD, 0, 0);
     avifRWStreamWriteU16(&s, (uint16_t)encoder->data->items.count); //  unsigned int(16) entry_count;
 
     for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
         avifEncoderItem * item = &encoder->data->items.item[itemIndex];
 
-        avifBoxMarker infe = avifRWStreamWriteBox(&s, "infe", 2, 0);
+        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;
@@ -439,8 +446,8 @@
     for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
         avifEncoderItem * item = &encoder->data->items.item[itemIndex];
         if (item->irefToID != 0) {
-            avifBoxMarker iref = avifRWStreamWriteBox(&s, "iref", 0, 0);
-            avifBoxMarker refType = avifRWStreamWriteBox(&s, item->irefType, -1, 0);
+            avifBoxMarker iref = avifRWStreamWriteFullBox(&s, "iref", AVIF_BOX_SIZE_TBD, 0, 0);
+            avifBoxMarker refType = avifRWStreamWriteBox(&s, item->irefType, AVIF_BOX_SIZE_TBD);
             avifRWStreamWriteU16(&s, item->id);       // unsigned int(16) from_item_ID;
             avifRWStreamWriteU16(&s, 1);              // unsigned int(16) reference_count;
             avifRWStreamWriteU16(&s, item->irefToID); // unsigned int(16) to_item_ID;
@@ -452,10 +459,10 @@
     // -----------------------------------------------------------------------
     // Write iprp -> ipco/ipma
 
-    avifBoxMarker iprp = avifRWStreamWriteBox(&s, "iprp", -1, 0);
+    avifBoxMarker iprp = avifRWStreamWriteBox(&s, "iprp", AVIF_BOX_SIZE_TBD);
 
     uint8_t itemPropertyIndex = 0;
-    avifBoxMarker ipco = avifRWStreamWriteBox(&s, "ipco", -1, 0);
+    avifBoxMarker ipco = avifRWStreamWriteBox(&s, "ipco", AVIF_BOX_SIZE_TBD);
     for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
         avifEncoderItem * item = &encoder->data->items.item[itemIndex];
         memset(&item->ipma, 0, sizeof(item->ipma));
@@ -466,14 +473,14 @@
 
         // Properties all av01 items need
 
-        avifBoxMarker ispe = avifRWStreamWriteBox(&s, "ispe", 0, 0);
+        avifBoxMarker ispe = avifRWStreamWriteFullBox(&s, "ispe", AVIF_BOX_SIZE_TBD, 0, 0);
         avifRWStreamWriteU32(&s, imageMetadata->width);  // unsigned int(32) image_width;
         avifRWStreamWriteU32(&s, imageMetadata->height); // unsigned int(32) image_height;
         avifRWStreamFinishBox(&s, ispe);
         ipmaPush(&item->ipma, ++itemPropertyIndex, AVIF_FALSE); // ipma is 1-indexed, doing this afterwards is correct
 
         uint8_t channelCount = item->alpha ? 1 : 3; // TODO: write the correct value here when adding monochrome support
-        avifBoxMarker pixi = avifRWStreamWriteBox(&s, "pixi", 0, 0);
+        avifBoxMarker pixi = avifRWStreamWriteFullBox(&s, "pixi", AVIF_BOX_SIZE_TBD, 0, 0);
         avifRWStreamWriteU8(&s, channelCount); // unsigned int (8) num_channels;
         for (uint8_t chan = 0; chan < channelCount; ++chan) {
             avifRWStreamWriteU8(&s, (uint8_t)imageMetadata->depth); // unsigned int (8) bits_per_channel;
@@ -487,7 +494,7 @@
         if (item->alpha) {
             // Alpha specific properties
 
-            avifBoxMarker auxC = avifRWStreamWriteBox(&s, "auxC", 0, 0);
+            avifBoxMarker auxC = avifRWStreamWriteFullBox(&s, "auxC", AVIF_BOX_SIZE_TBD, 0, 0);
             avifRWStreamWriteChars(&s, alphaURN, alphaURNSize); //  string aux_type;
             avifRWStreamFinishBox(&s, auxC);
             ipmaPush(&item->ipma, ++itemPropertyIndex, AVIF_FALSE);
@@ -495,13 +502,13 @@
             // Color specific properties
 
             if (imageMetadata->icc.data && (imageMetadata->icc.size > 0)) {
-                avifBoxMarker colr = avifRWStreamWriteBox(&s, "colr", -1, 0);
+                avifBoxMarker colr = avifRWStreamWriteBox(&s, "colr", AVIF_BOX_SIZE_TBD);
                 avifRWStreamWriteChars(&s, "prof", 4); // unsigned int(32) colour_type;
                 avifRWStreamWrite(&s, imageMetadata->icc.data, imageMetadata->icc.size);
                 avifRWStreamFinishBox(&s, colr);
                 ipmaPush(&item->ipma, ++itemPropertyIndex, AVIF_FALSE);
             } else {
-                avifBoxMarker colr = avifRWStreamWriteBox(&s, "colr", -1, 0);
+                avifBoxMarker colr = avifRWStreamWriteBox(&s, "colr", AVIF_BOX_SIZE_TBD);
                 avifRWStreamWriteChars(&s, "nclx", 4);                                      // unsigned int(32) colour_type;
                 avifRWStreamWriteU16(&s, (uint16_t)imageMetadata->colorPrimaries);          // unsigned int(16) colour_primaries;
                 avifRWStreamWriteU16(&s, (uint16_t)imageMetadata->transferCharacteristics); // unsigned int(16) transfer_characteristics;
@@ -514,14 +521,14 @@
 
             // Write (Optional) Transformations
             if (imageMetadata->transformFlags & AVIF_TRANSFORM_PASP) {
-                avifBoxMarker pasp = avifRWStreamWriteBox(&s, "pasp", -1, 0);
+                avifBoxMarker pasp = avifRWStreamWriteBox(&s, "pasp", AVIF_BOX_SIZE_TBD);
                 avifRWStreamWriteU32(&s, imageMetadata->pasp.hSpacing); // unsigned int(32) hSpacing;
                 avifRWStreamWriteU32(&s, imageMetadata->pasp.vSpacing); // unsigned int(32) vSpacing;
                 avifRWStreamFinishBox(&s, pasp);
                 ipmaPush(&item->ipma, ++itemPropertyIndex, AVIF_FALSE);
             }
             if (imageMetadata->transformFlags & AVIF_TRANSFORM_CLAP) {
-                avifBoxMarker clap = avifRWStreamWriteBox(&s, "clap", -1, 0);
+                avifBoxMarker clap = avifRWStreamWriteBox(&s, "clap", AVIF_BOX_SIZE_TBD);
                 avifRWStreamWriteU32(&s, imageMetadata->clap.widthN);    // unsigned int(32) cleanApertureWidthN;
                 avifRWStreamWriteU32(&s, imageMetadata->clap.widthD);    // unsigned int(32) cleanApertureWidthD;
                 avifRWStreamWriteU32(&s, imageMetadata->clap.heightN);   // unsigned int(32) cleanApertureHeightN;
@@ -534,14 +541,14 @@
                 ipmaPush(&item->ipma, ++itemPropertyIndex, AVIF_TRUE);
             }
             if (imageMetadata->transformFlags & AVIF_TRANSFORM_IROT) {
-                avifBoxMarker irot = avifRWStreamWriteBox(&s, "irot", -1, 0);
+                avifBoxMarker irot = avifRWStreamWriteBox(&s, "irot", AVIF_BOX_SIZE_TBD);
                 uint8_t angle = imageMetadata->irot.angle & 0x3;
                 avifRWStreamWrite(&s, &angle, 1); // unsigned int (6) reserved = 0; unsigned int (2) angle;
                 avifRWStreamFinishBox(&s, irot);
                 ipmaPush(&item->ipma, ++itemPropertyIndex, AVIF_TRUE);
             }
             if (imageMetadata->transformFlags & AVIF_TRANSFORM_IMIR) {
-                avifBoxMarker imir = avifRWStreamWriteBox(&s, "imir", -1, 0);
+                avifBoxMarker imir = avifRWStreamWriteBox(&s, "imir", AVIF_BOX_SIZE_TBD);
                 uint8_t axis = imageMetadata->imir.axis & 0x1;
                 avifRWStreamWrite(&s, &axis, 1); // unsigned int (7) reserved = 0; unsigned int (1) axis;
                 avifRWStreamFinishBox(&s, imir);
@@ -551,7 +558,7 @@
     }
     avifRWStreamFinishBox(&s, ipco);
 
-    avifBoxMarker ipma = avifRWStreamWriteBox(&s, "ipma", 0, 0);
+    avifBoxMarker ipma = avifRWStreamWriteFullBox(&s, "ipma", AVIF_BOX_SIZE_TBD, 0, 0);
     {
         int ipmaCount = 0;
         for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
@@ -589,23 +596,193 @@
     avifRWStreamFinishBox(&s, meta);
 
     // -----------------------------------------------------------------------
-    // Write tracks (if an image sequence
+    // Write tracks (if an image sequence)
 
     if (encoder->data->frames.count > 1) {
-        // TODO
+        static const uint32_t unityMatrix[9] = { 0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000 };
+
+        uint64_t durationInTimescales = 0;
+        for (uint32_t frameIndex = 0; frameIndex < encoder->data->frames.count; ++frameIndex) {
+            avifEncoderFrame * frame = &encoder->data->frames.frame[frameIndex];
+            durationInTimescales += frame->durationInTimescales;
+        }
+
+        // -------------------------------------------------------------------
+        // Start moov
+
+        avifBoxMarker moov = avifRWStreamWriteBox(&s, "moov", AVIF_BOX_SIZE_TBD);
+
+        avifBoxMarker mvhd = avifRWStreamWriteFullBox(&s, "mvhd", AVIF_BOX_SIZE_TBD, 1, 0);
+        avifRWStreamWriteU64(&s, now);                          // unsigned int(64) creation_time;
+        avifRWStreamWriteU64(&s, now);                          // unsigned int(64) modification_time;
+        avifRWStreamWriteU32(&s, (uint32_t)encoder->timescale); // unsigned int(32) timescale;
+        avifRWStreamWriteU64(&s, durationInTimescales);         // unsigned int(64) duration;
+        avifRWStreamWriteU32(&s, 0x00010000);                   // template int(32) rate = 0x00010000; // typically 1.0
+        avifRWStreamWriteU16(&s, 0x0100);                       // template int(16) volume = 0x0100; // typically, full volume
+        avifRWStreamWriteU16(&s, 0);                            // const bit(16) reserved = 0;
+        avifRWStreamWriteZeros(&s, 8);                          // const unsigned int(32)[2] reserved = 0;
+        avifRWStreamWrite(&s, (const uint8_t *)unityMatrix, sizeof(unityMatrix));
+        avifRWStreamWriteZeros(&s, 24);                       // bit(32)[6] pre_defined = 0;
+        avifRWStreamWriteU32(&s, encoder->data->items.count); // unsigned int(32) next_track_ID;
+        avifRWStreamFinishBox(&s, mvhd);
+
+        // -------------------------------------------------------------------
+        // Write tracks
+
+        for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
+            avifEncoderItem * item = &encoder->data->items.item[itemIndex];
+            if (item->encodeOutput->samples.count == 0) {
+                continue;
+            }
+
+            uint32_t syncSamplesCount = 0;
+            for (uint32_t sampleIndex = 0; sampleIndex < item->encodeOutput->samples.count; ++sampleIndex) {
+                avifEncodeSample * sample = &item->encodeOutput->samples.sample[sampleIndex];
+                if (sample->sync) {
+                    ++syncSamplesCount;
+                }
+            }
+
+            avifBoxMarker trak = avifRWStreamWriteBox(&s, "trak", AVIF_BOX_SIZE_TBD);
+
+            avifBoxMarker tkhd = avifRWStreamWriteFullBox(&s, "tkhd", AVIF_BOX_SIZE_TBD, 1, 0);
+            avifRWStreamWriteU64(&s, now);                    // unsigned int(64) creation_time;
+            avifRWStreamWriteU64(&s, now);                    // unsigned int(64) modification_time;
+            avifRWStreamWriteU32(&s, itemIndex + 1);          // unsigned int(32) track_ID;
+            avifRWStreamWriteU32(&s, 0);                      // const unsigned int(32) reserved = 0;
+            avifRWStreamWriteU64(&s, durationInTimescales);   // unsigned int(64) duration;
+            avifRWStreamWriteZeros(&s, sizeof(uint32_t) * 2); // const unsigned int(32)[2] reserved = 0;
+            avifRWStreamWriteU16(&s, 0);                      // template int(16) layer = 0;
+            avifRWStreamWriteU16(&s, 0);                      // template int(16) alternate_group = 0;
+            avifRWStreamWriteU16(&s, 0);                      // template int(16) volume = {if track_is_audio 0x0100 else 0};
+            avifRWStreamWriteU16(&s, 0);                      // const unsigned int(16) reserved = 0;
+            avifRWStreamWrite(&s, (const uint8_t *)unityMatrix, sizeof(unityMatrix)); // template int(32)[9] matrix= // { 0x00010000,0,0,0,0x00010000,0,0,0,0x40000000 };
+            avifRWStreamWriteU32(&s, imageMetadata->width << 16);  // unsigned int(32) width;
+            avifRWStreamWriteU32(&s, imageMetadata->height << 16); // unsigned int(32) height;
+            avifRWStreamFinishBox(&s, tkhd);
+
+            if (item->irefToID != 0) {
+                avifBoxMarker tref = avifRWStreamWriteBox(&s, "tref", AVIF_BOX_SIZE_TBD);
+                avifBoxMarker refType = avifRWStreamWriteBox(&s, item->irefType, AVIF_BOX_SIZE_TBD);
+                avifRWStreamWriteU32(&s, (uint32_t)item->irefToID);
+                avifRWStreamFinishBox(&s, refType);
+                avifRWStreamFinishBox(&s, tref);
+            }
+
+            avifBoxMarker mdia = avifRWStreamWriteBox(&s, "mdia", AVIF_BOX_SIZE_TBD);
+
+            avifBoxMarker mdhd = avifRWStreamWriteFullBox(&s, "mdhd", AVIF_BOX_SIZE_TBD, 1, 0);
+            avifRWStreamWriteU64(&s, now);                          // unsigned int(64) creation_time;
+            avifRWStreamWriteU64(&s, now);                          // unsigned int(64) modification_time;
+            avifRWStreamWriteU32(&s, (uint32_t)encoder->timescale); // unsigned int(32) timescale;
+            avifRWStreamWriteU64(&s, durationInTimescales);         // unsigned int(64) duration;
+            avifRWStreamWriteU16(&s, 21956);                        // bit(1) pad = 0; unsigned int(5)[3] language; ("und")
+            avifRWStreamWriteU16(&s, 0);                            // unsigned int(16) pre_defined = 0;
+            avifRWStreamFinishBox(&s, mdhd);
+
+            avifBoxMarker hdlrTrak = 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, hdlrTrak);
+
+            avifBoxMarker minf = avifRWStreamWriteBox(&s, "minf", AVIF_BOX_SIZE_TBD);
+
+            avifBoxMarker vmhd = avifRWStreamWriteFullBox(&s, "vmhd", AVIF_BOX_SIZE_TBD, 0, 1);
+            avifRWStreamWriteU16(&s, 0);   // template unsigned int(16) graphicsmode = 0; (copy over the existing image)
+            avifRWStreamWriteZeros(&s, 6); // template unsigned int(16)[3] opcolor = {0, 0, 0};
+            avifRWStreamFinishBox(&s, vmhd);
+
+            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)
+            avifRWStreamFinishBox(&s, stco);
+
+            avifBoxMarker stsc = avifRWStreamWriteFullBox(&s, "stsc", AVIF_BOX_SIZE_TBD, 0, 0);
+            avifRWStreamWriteU32(&s, 1);                                 // unsigned int(32) entry_count;
+            avifRWStreamWriteU32(&s, 1);                                 // unsigned int(32) first_chunk;
+            avifRWStreamWriteU32(&s, item->encodeOutput->samples.count); // unsigned int(32) samples_per_chunk;
+            avifRWStreamWriteU32(&s, 1);                                 // unsigned int(32) sample_description_index;
+            avifRWStreamFinishBox(&s, stsc);
+
+            avifBoxMarker stsz = avifRWStreamWriteFullBox(&s, "stsz", AVIF_BOX_SIZE_TBD, 0, 0);
+            avifRWStreamWriteU32(&s, 0);                                 // unsigned int(32) sample_size;
+            avifRWStreamWriteU32(&s, item->encodeOutput->samples.count); // unsigned int(32) sample_count;
+            for (uint32_t sampleIndex = 0; sampleIndex < item->encodeOutput->samples.count; ++sampleIndex) {
+                avifEncodeSample * sample = &item->encodeOutput->samples.sample[sampleIndex];
+                avifRWStreamWriteU32(&s, (uint32_t)sample->data.size); // unsigned int(32) entry_size;
+            }
+            avifRWStreamFinishBox(&s, stsz);
+
+            avifBoxMarker stss = avifRWStreamWriteFullBox(&s, "stss", AVIF_BOX_SIZE_TBD, 0, 0);
+            avifRWStreamWriteU32(&s, syncSamplesCount); // unsigned int(32) entry_count;
+            for (uint32_t sampleIndex = 0; sampleIndex < item->encodeOutput->samples.count; ++sampleIndex) {
+                avifEncodeSample * sample = &item->encodeOutput->samples.sample[sampleIndex];
+                if (sample->sync) {
+                    avifRWStreamWriteU32(&s, sampleIndex + 1); // unsigned int(32) sample_number;
+                }
+            }
+            avifRWStreamFinishBox(&s, stss);
+
+            // TODO: coalesce these
+            avifBoxMarker stts = avifRWStreamWriteFullBox(&s, "stts", AVIF_BOX_SIZE_TBD, 0, 0);
+            avifRWStreamWriteU32(&s, item->encodeOutput->samples.count); // unsigned int(32) entry_count;
+            for (uint32_t frameIndex = 0; frameIndex < encoder->data->frames.count; ++frameIndex) {
+                avifEncoderFrame * frame = &encoder->data->frames.frame[frameIndex];
+                avifRWStreamWriteU32(&s, 1);                                     // unsigned int(32) sample_count;
+                avifRWStreamWriteU32(&s, (uint32_t)frame->durationInTimescales); // unsigned int(32) sample_delta;
+            }
+            avifRWStreamFinishBox(&s, stts);
+
+            avifBoxMarker stsd = avifRWStreamWriteFullBox(&s, "stsd", AVIF_BOX_SIZE_TBD, 0, 0);
+            avifRWStreamWriteU32(&s, 1); // unsigned int(32) entry_count;
+            avifBoxMarker av01 = avifRWStreamWriteBox(&s, "av01", AVIF_BOX_SIZE_TBD);
+            avifRWStreamWriteZeros(&s, 6);                             // const unsigned int(8)[6] reserved = 0;
+            avifRWStreamWriteU16(&s, 1);                               // unsigned int(16) data_reference_index;
+            avifRWStreamWriteU16(&s, 0);                               // unsigned int(16) pre_defined = 0;
+            avifRWStreamWriteU16(&s, 0);                               // const unsigned int(16) reserved = 0;
+            avifRWStreamWriteZeros(&s, sizeof(uint32_t) * 3);          // unsigned int(32)[3] pre_defined = 0;
+            avifRWStreamWriteU16(&s, (uint16_t)imageMetadata->width);  // unsigned int(16) width;
+            avifRWStreamWriteU16(&s, (uint16_t)imageMetadata->height); // unsigned int(16) height;
+            avifRWStreamWriteU32(&s, 0x00480000);                      // template unsigned int(32) horizresolution
+            avifRWStreamWriteU32(&s, 0x00480000);                      // template unsigned int(32) vertresolution
+            avifRWStreamWriteU32(&s, 0);                               // const unsigned int(32) reserved = 0;
+            avifRWStreamWriteU16(&s, 1);                               // template unsigned int(16) frame_count = 1;
+            avifRWStreamWriteZeros(&s, 32);                            // string[32] compressorname;
+            avifRWStreamWriteU16(&s, 0x0018);                          // template unsigned int(16) depth = 0x0018;
+            avifRWStreamWriteU16(&s, (uint16_t)0xffff);                // int(16) pre_defined = -1;
+            writeConfigBox(&s, &item->codec->configBox);
+            avifRWStreamFinishBox(&s, av01);
+            avifRWStreamFinishBox(&s, stsd);
+
+            avifRWStreamFinishBox(&s, stbl);
+
+            avifRWStreamFinishBox(&s, minf);
+            avifRWStreamFinishBox(&s, mdia);
+            avifRWStreamFinishBox(&s, trak);
+        }
+
+        // -------------------------------------------------------------------
+        // Finish moov box
+
+        avifRWStreamFinishBox(&s, moov);
     }
 
     // -----------------------------------------------------------------------
     // Write mdat
 
-    avifBoxMarker mdat = avifRWStreamWriteBox(&s, "mdat", -1, 0);
+    avifBoxMarker mdat = avifRWStreamWriteBox(&s, "mdat", AVIF_BOX_SIZE_TBD);
     for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
         avifEncoderItem * item = &encoder->data->items.item[itemIndex];
         if ((item->content.size == 0) && (item->encodeOutput->samples.count == 0)) {
             continue;
         }
 
-        uint32_t infeOffset = (uint32_t)s.offset;
+        uint32_t chunkOffset = (uint32_t)s.offset;
         if (item->encodeOutput->samples.count > 0) {
             for (uint32_t sampleIndex = 0; sampleIndex < item->encodeOutput->samples.count; ++sampleIndex) {
                 avifEncodeSample * sample = &item->encodeOutput->samples.sample[sampleIndex];
@@ -618,7 +795,14 @@
         if (item->infeOffsetOffset != 0) {
             size_t prevOffset = avifRWStreamOffset(&s);
             avifRWStreamSetOffset(&s, item->infeOffsetOffset);
-            avifRWStreamWriteU32(&s, infeOffset);
+            avifRWStreamWriteU32(&s, chunkOffset);
+            avifRWStreamSetOffset(&s, prevOffset);
+        }
+
+        if (item->stcoOffsetOffset != 0) {
+            size_t prevOffset = avifRWStreamOffset(&s);
+            avifRWStreamSetOffset(&s, item->stcoOffsetOffset);
+            avifRWStreamWriteU32(&s, chunkOffset);
             avifRWStreamSetOffset(&s, prevOffset);
         }
     }
@@ -634,7 +818,7 @@
 
 avifResult avifEncoderWrite(avifEncoder * encoder, const avifImage * image, avifRWData * output)
 {
-    avifResult addImageResult = avifEncoderAddImage(encoder, image, NULL);
+    avifResult addImageResult = avifEncoderAddImage(encoder, image, 1);
     if (addImageResult != AVIF_RESULT_OK) {
         return addImageResult;
     }
@@ -738,7 +922,7 @@
 
 static void writeConfigBox(avifRWStream * s, avifCodecConfigurationBox * cfg)
 {
-    avifBoxMarker av1C = avifRWStreamWriteBox(s, "av1C", -1, 0);
+    avifBoxMarker av1C = avifRWStreamWriteBox(s, "av1C", AVIF_BOX_SIZE_TBD);
 
     // unsigned int (1) marker = 1;
     // unsigned int (7) version = 1;