Rework codec layer to allow avifEncoderAddImage() to choose keyframes. Rework rav1e frame pump to not bail out early
diff --git a/src/codec_aom.c b/src/codec_aom.c
index 7265e60..7802d6d 100644
--- a/src/codec_aom.c
+++ b/src/codec_aom.c
@@ -235,7 +235,12 @@
     return fmt;
 }
 
-static avifBool aomCodecEncodeImage(avifCodec * codec, const avifImage * image, avifEncoder * encoder, avifBool alpha, avifCodecEncodeOutput * output)
+static avifBool aomCodecEncodeImage(avifCodec * codec,
+                                    const avifImage * image,
+                                    avifEncoder * encoder,
+                                    avifBool alpha,
+                                    avifBool forceKeyframe,
+                                    avifCodecEncodeOutput * output)
 {
     if (!codec->internal->encoderInitialized) {
         // Map encoder speed to AOM usage + CpuUsed:
@@ -303,6 +308,7 @@
         if (encoder->maxThreads > 1) {
             cfg.g_threads = encoder->maxThreads;
         }
+        cfg.kf_mode = AOM_KF_DISABLED;
 
         int minQuantizer = AVIF_CLAMP(encoder->minQuantizer, 0, 63);
         int maxQuantizer = AVIF_CLAMP(encoder->maxQuantizer, 0, 63);
@@ -391,7 +397,11 @@
         aom_codec_control(&codec->internal->encoder, AV1E_SET_MATRIX_COEFFICIENTS, aomImage->mc);
     }
 
-    aom_codec_encode(&codec->internal->encoder, aomImage, 0, 1, 0);
+    aom_enc_frame_flags_t encodeFlags = 0;
+    if (forceKeyframe) {
+        encodeFlags |= AOM_EFLAG_FORCE_KF;
+    }
+    aom_codec_encode(&codec->internal->encoder, aomImage, 0, 1, encodeFlags);
 
     aom_codec_iter_t iter = NULL;
     for (;;) {
diff --git a/src/codec_rav1e.c b/src/codec_rav1e.c
index 85e3376..9cdf133 100644
--- a/src/codec_rav1e.c
+++ b/src/codec_rav1e.c
@@ -31,10 +31,13 @@
     return AVIF_TRUE;
 }
 
-static avifBool rav1eCodecEncodeImage(avifCodec * codec, const avifImage * image, avifEncoder * encoder, avifBool alpha, avifCodecEncodeOutput * output)
+static avifBool rav1eCodecEncodeImage(avifCodec * codec,
+                                      const avifImage * image,
+                                      avifEncoder * encoder,
+                                      avifBool alpha,
+                                      avifBool forceKeyframe,
+                                      avifCodecEncodeOutput * output)
 {
-    (void)codec; // unused
-
     avifBool success = AVIF_FALSE;
 
     RaConfig * rav1eConfig = NULL;
@@ -149,18 +152,29 @@
         }
     }
 
+    RaFrameTypeOverride frameType = RA_FRAME_TYPE_OVERRIDE_NO;
+    if (forceKeyframe) {
+        frameType = RA_FRAME_TYPE_OVERRIDE_KEY;
+    }
+    rav1e_frame_set_type(rav1eFrame, frameType);
+
     RaEncoderStatus encoderStatus = rav1e_send_frame(codec->internal->rav1eContext, rav1eFrame);
-    if (encoderStatus != 0) {
+    if (encoderStatus != RA_ENCODER_STATUS_SUCCESS) {
         goto cleanup;
     }
 
     RaPacket * pkt = NULL;
     for (;;) {
         encoderStatus = rav1e_receive_packet(codec->internal->rav1eContext, &pkt);
-        if ((encoderStatus != 0) && (encoderStatus != RA_ENCODER_STATUS_NEED_MORE_DATA)) {
+        if (encoderStatus == RA_ENCODER_STATUS_ENCODED) {
+            continue;
+        }
+        if ((encoderStatus != RA_ENCODER_STATUS_SUCCESS) && (encoderStatus != RA_ENCODER_STATUS_NEED_MORE_DATA)) {
             goto cleanup;
-        } else if (pkt && pkt->data && (pkt->len > 0)) {
-            avifCodecEncodeOutputAddSample(output, pkt->data, pkt->len, (pkt->frame_type == RA_FRAME_TYPE_KEY));
+        } else if (pkt) {
+            if (pkt->data && (pkt->len > 0)) {
+                avifCodecEncodeOutputAddSample(output, pkt->data, pkt->len, (pkt->frame_type == RA_FRAME_TYPE_KEY));
+            }
             rav1e_packet_unref(pkt);
             pkt = NULL;
         } else {
@@ -184,7 +198,7 @@
 {
     for (;;) {
         RaEncoderStatus encoderStatus = rav1e_send_frame(codec->internal->rav1eContext, NULL); // flush
-        if (encoderStatus != 0) {
+        if (encoderStatus != RA_ENCODER_STATUS_SUCCESS) {
             return AVIF_FALSE;
         }
 
@@ -192,13 +206,17 @@
         RaPacket * pkt = NULL;
         for (;;) {
             encoderStatus = rav1e_receive_packet(codec->internal->rav1eContext, &pkt);
-            if ((encoderStatus != 0) && (encoderStatus != RA_ENCODER_STATUS_LIMIT_REACHED) &&
-                (encoderStatus != RA_ENCODER_STATUS_ENCODED)) {
+            if (encoderStatus == RA_ENCODER_STATUS_ENCODED) {
+                continue;
+            }
+            if ((encoderStatus != RA_ENCODER_STATUS_SUCCESS) && (encoderStatus != RA_ENCODER_STATUS_LIMIT_REACHED)) {
                 return AVIF_FALSE;
             }
-            if (pkt && pkt->data && (pkt->len > 0)) {
+            if (pkt) {
                 gotPacket = AVIF_TRUE;
-                avifCodecEncodeOutputAddSample(output, pkt->data, pkt->len, (pkt->frame_type == RA_FRAME_TYPE_KEY));
+                if (pkt->data && (pkt->len > 0)) {
+                    avifCodecEncodeOutputAddSample(output, pkt->data, pkt->len, (pkt->frame_type == RA_FRAME_TYPE_KEY));
+                }
                 rav1e_packet_unref(pkt);
                 pkt = NULL;
             } else {
diff --git a/src/write.c b/src/write.c
index 0c01aae..b666d65 100644
--- a/src/write.c
+++ b/src/write.c
@@ -446,10 +446,15 @@
     // -----------------------------------------------------------------------
     // Encode AV1 OBUs
 
+    avifBool forceKeyframe = AVIF_FALSE;
+    if ((encoder->data->frames.count % 8) == 0) { // TODO: make this configurable
+        forceKeyframe = AVIF_TRUE;
+    }
+
     for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
         avifEncoderItem * item = &encoder->data->items.item[itemIndex];
         if (item->codec) {
-            if (!item->codec->encodeImage(item->codec, image, encoder, item->alpha, item->encodeOutput)) {
+            if (!item->codec->encodeImage(item->codec, image, encoder, item->alpha, forceKeyframe, item->encodeOutput)) {
                 return item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED;
             }
         }