Commit 86beddfc for libheif

commit 86beddfcd1db072b8c60d49f672287abda1dee50
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Tue May 19 17:15:27 2026 +0200

    add heif_sequence_content_kind enum to differentiate video content from slideshows (#1725)

diff --git a/libheif/api/libheif/heif_sequences.cc b/libheif/api/libheif/heif_sequences.cc
index b8640758..e628adff 100644
--- a/libheif/api/libheif/heif_sequences.cc
+++ b/libheif/api/libheif/heif_sequences.cc
@@ -26,6 +26,7 @@
 #include "sequences/track_visual.h"
 #include "sequences/track_metadata.h"

+#include <algorithm>
 #include <array>
 #include <cstring>
 #include <memory>
@@ -404,7 +405,7 @@ heif_sequence_encoding_options* heif_sequence_encoding_options_alloc()
 {
   heif_sequence_encoding_options* options = new heif_sequence_encoding_options();

-  options->version = 2;
+  options->version = 3;
   options->output_nclx_profile = nullptr;

   options->color_conversion_options.version = 1;
@@ -419,10 +420,41 @@ heif_sequence_encoding_options* heif_sequence_encoding_options_alloc()
   options->keyframe_distance_max = 0;
   options->save_alpha_channel = 1;

+  // version 3
+
+  options->content_kind = heif_sequence_content_kind_auto;
+
   return options;
 }


+// overwrite the (possibly lower version) input options over the default options
+void heif_sequence_encoding_options_copy(heif_sequence_encoding_options* dst,
+                                         const heif_sequence_encoding_options* src)
+{
+  if (src == nullptr) {
+    return;
+  }
+
+  int min_version = std::min(dst->version, src->version);
+
+  switch (min_version) {
+    case 3:
+      dst->content_kind = src->content_kind;
+      [[fallthrough]];
+    case 2:
+      dst->gop_structure = src->gop_structure;
+      dst->keyframe_distance_min = src->keyframe_distance_min;
+      dst->keyframe_distance_max = src->keyframe_distance_max;
+      dst->save_alpha_channel = src->save_alpha_channel;
+      [[fallthrough]];
+    case 1:
+      dst->output_nclx_profile = src->output_nclx_profile;
+      dst->color_conversion_options = src->color_conversion_options;
+  }
+}
+
+
 void heif_sequence_encoding_options_release(heif_sequence_encoding_options* options)
 {
   delete options;
diff --git a/libheif/api/libheif/heif_sequences.h b/libheif/api/libheif/heif_sequences.h
index 3637cba4..a04d4754 100644
--- a/libheif/api/libheif/heif_sequences.h
+++ b/libheif/api/libheif/heif_sequences.h
@@ -392,6 +392,23 @@ typedef enum heif_sequence_gop_structure
 } heif_sequence_gop_structure;


+// Describes the intent of the encoded sequence content. Encoder plugins may
+// use this to choose different default tunings (e.g. perceptual quality vs.
+// rate-distortion) for slide-show-style image sequences vs. video.
+//
+// Pass `_auto` to let libheif pick a value. Today libheif derives the kind
+// from the track's handler type (`pict` -> image_sequence, `vide` -> video);
+// in the future it may use additional input signals (e.g. frame rate or
+// frame-to-frame similarity). Plugins never see `_auto`: libheif resolves it
+// to a concrete kind before passing the options to the encoder plugin.
+typedef enum heif_sequence_content_kind
+{
+  heif_sequence_content_kind_auto = 0,
+  heif_sequence_content_kind_image_sequence = 1,
+  heif_sequence_content_kind_video = 2
+} heif_sequence_content_kind;
+
+
 typedef struct heif_sequence_encoding_options
 {
   uint8_t version;
@@ -411,12 +428,32 @@ typedef struct heif_sequence_encoding_options
   int keyframe_distance_max; // 0 - undefined

   int save_alpha_channel;
+
+  // version 3 options
+
+  // Intent of the encoded content. Encoder plugins may use this to choose
+  // different tunings for slide-show-style image sequences vs. video.
+  // Set to `_auto` (the default) to let libheif pick. libheif resolves the
+  // value to a concrete kind before passing the options to the encoder
+  // plugin, so plugins never see `_auto`.
+  enum heif_sequence_content_kind content_kind;
 } heif_sequence_encoding_options;


 LIBHEIF_API
 heif_sequence_encoding_options* heif_sequence_encoding_options_alloc(void);

+/**
+ * Copy fields from `src` into `dst`, respecting both structs' version numbers.
+ * Only fields present in `min(dst->version, src->version)` are copied, so this
+ * is safe when libheif and the caller were built against different header
+ * versions of `heif_sequence_encoding_options`. Pass NULL `src` to leave `dst`
+ * unchanged.
+ */
+LIBHEIF_API
+void heif_sequence_encoding_options_copy(heif_sequence_encoding_options* dst,
+                                         const heif_sequence_encoding_options* src);
+
 LIBHEIF_API
 void heif_sequence_encoding_options_release(heif_sequence_encoding_options*);

diff --git a/libheif/plugins/encoder_aom.cc b/libheif/plugins/encoder_aom.cc
index 1037f046..2d480c72 100644
--- a/libheif/plugins/encoder_aom.cc
+++ b/libheif/plugins/encoder_aom.cc
@@ -1060,11 +1060,17 @@ static heif_error aom_start_sequence_encoding_intern(void* encoder_raw, const he
   // make sure NCLX profile is deleted at end of function
   auto nclx_deleter = std::unique_ptr<heif_color_profile_nclx, void (*)(heif_color_profile_nclx*)>(nclx, heif_nclx_color_profile_free);

+  // A slide-show-style image sequence is treated like a still image (favor
+  // perceptual quality / AOM_TUNE_IQ when supported); only true video content
+  // keeps SSIM-tuned encoding.
+  bool tune_as_video = (image_sequence &&
+                        options &&
+                        options->version >= 3 &&
+                        options->content_kind == heif_sequence_content_kind_video);
+
   aom_tune_metric effective_tune = encoder->tune;
   if (encoder->tune_auto) {
-    if (image_sequence) {
-      // TODO: we might add a flag to differentiate "image_sequences" (slide show) vs. video.
-      //       Then we could use AOM_TUNE_IQ for slide show sequences and SSIM for video.
+    if (tune_as_video) {
       effective_tune = AOM_TUNE_SSIM;
     }
     else if (input_class == heif_image_input_class_alpha) {
diff --git a/libheif/sequences/track_visual.cc b/libheif/sequences/track_visual.cc
index 425faa7a..66d325e0 100644
--- a/libheif/sequences/track_visual.cc
+++ b/libheif/sequences/track_visual.cc
@@ -516,19 +516,38 @@ Error Track_Visual::encode_image(std::shared_ptr<HeifPixelImage> image,

   // --- encode image

-  heif_sequence_encoding_options* local_dummy_options = nullptr;
-  if (!in_options) {
-    local_dummy_options = heif_sequence_encoding_options_alloc();
+  // Build an effective options struct so libheif can resolve "auto" fields
+  // (e.g. content_kind) to concrete values before passing them to the encoder
+  // plugin. heif_sequence_encoding_options_copy is version-aware, so callers
+  // built against older headers won't be read past their actual allocation.
+
+  std::unique_ptr<heif_sequence_encoding_options, void(*)(heif_sequence_encoding_options*)>
+      effective_options(heif_sequence_encoding_options_alloc(),
+                        heif_sequence_encoding_options_release);
+  heif_sequence_encoding_options_copy(effective_options.get(), in_options);
+
+  // Resolve content_kind=auto based on this track's handler type. For auxiliary
+  // tracks (e.g. alpha) the parent track resolves the value before recursing in,
+  // so we never reach this branch with handler=auxv.
+  // TODO: in the future, "auto" could also factor in input frame rate or
+  // frame-to-frame similarity.
+  if (effective_options->content_kind == heif_sequence_content_kind_auto) {
+    switch (get_handler()) {
+      case heif_track_type_video:
+        effective_options->content_kind = heif_sequence_content_kind_video;
+        break;
+      case heif_track_type_image_sequence:
+      default:
+        effective_options->content_kind = heif_sequence_content_kind_image_sequence;
+        break;
+    }
   }

   Error encodeError = encoder->encode_sequence_frame(colorConvertedImage, h_encoder,
-                                                     in_options ? *in_options : *local_dummy_options,
+                                                     *effective_options,
                                                      input_class,
                                                      colorConvertedImage->get_sample_duration(), get_timescale(),
                                                      m_current_frame_nr);
-  if (local_dummy_options) {
-    heif_sequence_encoding_options_release(local_dummy_options);
-  }

   if (encodeError) {
     return encodeError;
@@ -575,9 +594,12 @@ Error Track_Visual::encode_image(std::shared_ptr<HeifPixelImage> image,

     (*alphaImageResult)->set_sample_duration(colorConvertedImage->get_sample_duration());

+    // Pass the resolved options (not the caller's in_options) so the alpha aux
+    // track inherits the color track's content_kind instead of re-resolving
+    // from its own 'auxv' handler.
     auto err = m_aux_alpha_track->encode_image(*alphaImageResult,
                                                m_alpha_track_encoder.get(),
-                                               in_options,
+                                               effective_options.get(),
                                                heif_image_input_class_alpha);
     if (err) {
       return err;