Codec-specific options plumbing for advanced encoding settings (unused, first pass)
diff --git a/src/avif.c b/src/avif.c
index 969accc..9c9fe04 100644
--- a/src/avif.c
+++ b/src/avif.c
@@ -68,24 +68,25 @@
 {
     // clang-format off
     switch (result) {
-        case AVIF_RESULT_OK:                        return "OK";
-        case AVIF_RESULT_INVALID_FTYP:              return "Invalid ftyp";
-        case AVIF_RESULT_NO_CONTENT:                return "No content";
-        case AVIF_RESULT_NO_YUV_FORMAT_SELECTED:    return "No YUV format selected";
-        case AVIF_RESULT_REFORMAT_FAILED:           return "Reformat failed";
-        case AVIF_RESULT_UNSUPPORTED_DEPTH:         return "Unsupported depth";
-        case AVIF_RESULT_ENCODE_COLOR_FAILED:       return "Encoding of color planes failed";
-        case AVIF_RESULT_ENCODE_ALPHA_FAILED:       return "Encoding of alpha plane failed";
-        case AVIF_RESULT_BMFF_PARSE_FAILED:         return "BMFF parsing failed";
-        case AVIF_RESULT_NO_AV1_ITEMS_FOUND:        return "No AV1 items found";
-        case AVIF_RESULT_DECODE_COLOR_FAILED:       return "Decoding of color planes failed";
-        case AVIF_RESULT_DECODE_ALPHA_FAILED:       return "Decoding of alpha plane failed";
-        case AVIF_RESULT_COLOR_ALPHA_SIZE_MISMATCH: return "Color and alpha planes size mismatch";
-        case AVIF_RESULT_ISPE_SIZE_MISMATCH:        return "Plane sizes don't match ispe values";
-        case AVIF_RESULT_NO_CODEC_AVAILABLE:        return "No codec available";
-        case AVIF_RESULT_NO_IMAGES_REMAINING:       return "No images remaining";
-        case AVIF_RESULT_INVALID_EXIF_PAYLOAD:      return "Invalid Exif payload";
-        case AVIF_RESULT_INVALID_IMAGE_GRID:        return "Invalid image grid";
+        case AVIF_RESULT_OK:                            return "OK";
+        case AVIF_RESULT_INVALID_FTYP:                  return "Invalid ftyp";
+        case AVIF_RESULT_NO_CONTENT:                    return "No content";
+        case AVIF_RESULT_NO_YUV_FORMAT_SELECTED:        return "No YUV format selected";
+        case AVIF_RESULT_REFORMAT_FAILED:               return "Reformat failed";
+        case AVIF_RESULT_UNSUPPORTED_DEPTH:             return "Unsupported depth";
+        case AVIF_RESULT_ENCODE_COLOR_FAILED:           return "Encoding of color planes failed";
+        case AVIF_RESULT_ENCODE_ALPHA_FAILED:           return "Encoding of alpha plane failed";
+        case AVIF_RESULT_BMFF_PARSE_FAILED:             return "BMFF parsing failed";
+        case AVIF_RESULT_NO_AV1_ITEMS_FOUND:            return "No AV1 items found";
+        case AVIF_RESULT_DECODE_COLOR_FAILED:           return "Decoding of color planes failed";
+        case AVIF_RESULT_DECODE_ALPHA_FAILED:           return "Decoding of alpha plane failed";
+        case AVIF_RESULT_COLOR_ALPHA_SIZE_MISMATCH:     return "Color and alpha planes size mismatch";
+        case AVIF_RESULT_ISPE_SIZE_MISMATCH:            return "Plane sizes don't match ispe values";
+        case AVIF_RESULT_NO_CODEC_AVAILABLE:            return "No codec available";
+        case AVIF_RESULT_NO_IMAGES_REMAINING:           return "No images remaining";
+        case AVIF_RESULT_INVALID_EXIF_PAYLOAD:          return "Invalid Exif payload";
+        case AVIF_RESULT_INVALID_IMAGE_GRID:            return "Invalid image grid";
+        case AVIF_RESULT_INVALID_CODEC_SPECIFIC_OPTION: return "Invalid codec-specific option";
         case AVIF_RESULT_UNKNOWN_ERROR:
         default:
             break;
@@ -376,6 +377,79 @@
 }
 
 // ---------------------------------------------------------------------------
+// avifCodecSpecificOption
+
+static char * avifStrdup(const char * str)
+{
+    size_t len = strlen(str);
+    char * dup = avifAlloc(len + 1);
+    memcpy(dup, str, len + 1);
+    return dup;
+}
+
+avifCodecSpecificOptions * avifCodecSpecificOptionsCreate(void)
+{
+    avifCodecSpecificOptions * ava = avifAlloc(sizeof(avifCodecSpecificOptions));
+    avifArrayCreate(ava, sizeof(avifCodecSpecificOption), 4);
+    return ava;
+}
+
+void avifCodecSpecificOptionsDestroy(avifCodecSpecificOptions * csOptions)
+{
+    if (!csOptions) {
+        return;
+    }
+
+    for (uint32_t i = 0; i < csOptions->count; ++i) {
+        avifCodecSpecificOption * entry = &csOptions->entries[i];
+        avifFree(entry->key);
+        avifFree(entry->value);
+    }
+    avifArrayDestroy(csOptions);
+    avifFree(csOptions);
+}
+
+void avifCodecSpecificOptionsSet(avifCodecSpecificOptions * csOptions, const char * key, const char * value)
+{
+    // Check to see if a key must be replaced
+    for (uint32_t i = 0; i < csOptions->count; ++i) {
+        avifCodecSpecificOption * entry = &csOptions->entries[i];
+        if (!strcmp(entry->key, key)) {
+            if (value) {
+                // Update the value
+                avifFree(entry->value);
+                entry->value = avifStrdup(value);
+            } else {
+                // Delete the value
+                avifFree(entry->key);
+                avifFree(entry->value);
+                --csOptions->count;
+                if (csOptions->count > 0) {
+                    memmove(&csOptions->entries[i], &csOptions->entries[i + 1], (csOptions->count - i) * csOptions->elementSize);
+                }
+            }
+            return;
+        }
+    }
+
+    // Add a new key
+    avifCodecSpecificOption * entry = (avifCodecSpecificOption *)avifArrayPushPtr(csOptions);
+    entry->key = avifStrdup(key);
+    entry->value = avifStrdup(value);
+}
+
+avifCodecSpecificOption * avifCodecSpecificOptionsGet(avifCodecSpecificOptions * csOptions, const char * key)
+{
+    for (uint32_t i = 0; i < csOptions->count; ++i) {
+        avifCodecSpecificOption * entry = &csOptions->entries[i];
+        if (!strcmp(entry->key, key)) {
+            return entry;
+        }
+    }
+    return NULL;
+}
+
+// ---------------------------------------------------------------------------
 // Codec availability and versions
 
 typedef const char * (*versionFunc)(void);
diff --git a/src/codec_aom.c b/src/codec_aom.c
index 10bc8e8..4e602e3 100644
--- a/src/codec_aom.c
+++ b/src/codec_aom.c
@@ -216,12 +216,12 @@
     return fmt;
 }
 
-static avifBool aomCodecEncodeImage(avifCodec * codec,
-                                    avifEncoder * encoder,
-                                    const avifImage * image,
-                                    avifBool alpha,
-                                    uint32_t addImageFlags,
-                                    avifCodecEncodeOutput * output)
+static avifResult aomCodecEncodeImage(avifCodec * codec,
+                                      avifEncoder * encoder,
+                                      const avifImage * image,
+                                      avifBool alpha,
+                                      uint32_t addImageFlags,
+                                      avifCodecEncodeOutput * output)
 {
     if (!codec->internal->encoderInitialized) {
         // Map encoder speed to AOM usage + CpuUsed:
@@ -271,9 +271,14 @@
             }
         }
 
+        if (codec->csOptions->count > 0) {
+            // None are currently supported!
+            return AVIF_RESULT_INVALID_CODEC_SPECIFIC_OPTION;
+        }
+
         codec->internal->aomFormat = avifImageCalcAOMFmt(image, alpha);
         if (codec->internal->aomFormat == AOM_IMG_FMT_NONE) {
-            return AVIF_FALSE;
+            return AVIF_RESULT_UNKNOWN_ERROR;
         }
 
         avifGetPixelFormatInfo(image->yuvFormat, &codec->internal->formatInfo);
@@ -436,7 +441,7 @@
     }
 
     aom_img_free(aomImage);
-    return AVIF_TRUE;
+    return AVIF_RESULT_OK;
 }
 
 static avifBool aomCodecEncodeFinish(avifCodec * codec, avifCodecEncodeOutput * output)
diff --git a/src/codec_rav1e.c b/src/codec_rav1e.c
index 6a7fb22..f7605ef 100644
--- a/src/codec_rav1e.c
+++ b/src/codec_rav1e.c
@@ -55,19 +55,24 @@
     return minorVersion >= 4;
 }
 
-static avifBool rav1eCodecEncodeImage(avifCodec * codec,
-                                      avifEncoder * encoder,
-                                      const avifImage * image,
-                                      avifBool alpha,
-                                      uint32_t addImageFlags,
-                                      avifCodecEncodeOutput * output)
+static avifResult rav1eCodecEncodeImage(avifCodec * codec,
+                                        avifEncoder * encoder,
+                                        const avifImage * image,
+                                        avifBool alpha,
+                                        uint32_t addImageFlags,
+                                        avifCodecEncodeOutput * output)
 {
-    avifBool success = AVIF_FALSE;
+    avifResult result = AVIF_RESULT_UNKNOWN_ERROR;
 
     RaConfig * rav1eConfig = NULL;
     RaFrame * rav1eFrame = NULL;
 
     if (!codec->internal->rav1eContext) {
+        if (codec->csOptions->count > 0) {
+            // None are currently supported!
+            return AVIF_RESULT_INVALID_CODEC_SPECIFIC_OPTION;
+        }
+
         const avifBool supports400 = rav1eSupports400();
         RaPixelRange rav1eRange;
         if (alpha) {
@@ -94,7 +99,7 @@
                     break;
                 case AVIF_PIXEL_FORMAT_NONE:
                 default:
-                    return AVIF_FALSE;
+                    return AVIF_RESULT_UNKNOWN_ERROR;
             }
         }
 
@@ -209,7 +214,7 @@
             break;
         }
     }
-    success = AVIF_TRUE;
+    result = AVIF_RESULT_OK;
 cleanup:
     if (rav1eFrame) {
         rav1e_frame_unref(rav1eFrame);
@@ -219,7 +224,7 @@
         rav1e_config_unref(rav1eConfig);
         rav1eConfig = NULL;
     }
-    return success;
+    return result;
 }
 
 static avifBool rav1eCodecEncodeFinish(avifCodec * codec, avifCodecEncodeOutput * output)
diff --git a/src/write.c b/src/write.c
index d3ac127..3b8493d 100644
--- a/src/write.c
+++ b/src/write.c
@@ -178,15 +178,22 @@
     encoder->data = avifEncoderDataCreate();
     encoder->timescale = 1;
     encoder->keyframeInterval = 0;
+    encoder->csOptions = avifCodecSpecificOptionsCreate();
     return encoder;
 }
 
 void avifEncoderDestroy(avifEncoder * encoder)
 {
+    avifCodecSpecificOptionsDestroy(encoder->csOptions);
     avifEncoderDataDestroy(encoder->data);
     avifFree(encoder);
 }
 
+void avifEncoderSetCodecSpecificOption(avifEncoder * encoder, const char * key, const char * value)
+{
+    avifCodecSpecificOptionsSet(encoder->csOptions, key, value);
+}
+
 static void avifEncoderWriteColorProperties(avifRWStream * s, const avifImage * imageMetadata, struct ipmaArray * ipma, uint8_t * itemPropertyIndex)
 {
     if (imageMetadata->icc.size > 0) {
@@ -359,6 +366,7 @@
 
         encoder->data->colorItem = avifEncoderDataCreateItem(encoder->data, "av01", "Color", 6);
         encoder->data->colorItem->codec = avifCodecCreate(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE);
+        encoder->data->colorItem->codec->csOptions = encoder->csOptions;
         if (!encoder->data->colorItem->codec) {
             // Just bail out early, we're not surviving this function without an encoder compiled in
             return AVIF_RESULT_NO_CODEC_AVAILABLE;
@@ -381,6 +389,7 @@
         if (needsAlpha) {
             encoder->data->alphaItem = avifEncoderDataCreateItem(encoder->data, "av01", "Alpha", 6);
             encoder->data->alphaItem->codec = avifCodecCreate(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE);
+            encoder->data->alphaItem->codec->csOptions = encoder->csOptions;
             if (!encoder->data->alphaItem->codec) {
                 return AVIF_RESULT_NO_CODEC_AVAILABLE;
             }
@@ -465,8 +474,13 @@
     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, encoder, image, item->alpha, addImageFlags, item->encodeOutput)) {
-                return item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED;
+            avifResult encodeResult =
+                item->codec->encodeImage(item->codec, encoder, image, item->alpha, addImageFlags, item->encodeOutput);
+            if (encodeResult == AVIF_RESULT_UNKNOWN_ERROR) {
+                encodeResult = item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED;
+            }
+            if (encodeResult != AVIF_RESULT_OK) {
+                return encodeResult;
             }
         }
     }