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;