This is an automated email from the git hooks/post-receive script. Git pushed a commit to branch master in repository ffmpeg.
commit 00249664deb532efeedfd25a300f030c7bcec982 Author: James Almer <[email protected]> AuthorDate: Mon Oct 5 16:13:51 2026 -0300 Commit: James Almer <[email protected]> CommitDate: Mon Oct 12 10:39:17 2026 -0300 avformat/mov: support exporting derived images of type tmap as Gain Map Stream Groups Signed-off-by: James Almer <[email protected]> --- libavformat/isom.h | 4 + libavformat/mov.c | 351 +++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 316 insertions(+), 39 deletions(-) diff --git a/libavformat/isom.h b/libavformat/isom.h index 2516d65d78..dad5acf961 100644 --- a/libavformat/isom.h +++ b/libavformat/isom.h @@ -297,8 +297,10 @@ typedef struct HEIFItemRef { typedef struct HEIFItem { AVStream *st; + AVStreamGroup *stg; HEIFItemRef *iref_list; int nb_iref_list; + int flags; char *name; int item_id; int64_t extent_length; @@ -435,6 +437,8 @@ void ff_mp4_parse_es_descr(AVIOContext *pb, int *es_id); #define MOV_TKHD_FLAG_IN_PREVIEW 0x0004 #define MOV_TKHD_FLAG_IN_POSTER 0x0008 +#define MOV_INFE_FLAG_HIDDEN 0x01 + #define MOV_SAMPLE_DEPENDENCY_UNKNOWN 0x0 #define MOV_SAMPLE_DEPENDENCY_YES 0x1 #define MOV_SAMPLE_DEPENDENCY_NO 0x2 diff --git a/libavformat/mov.c b/libavformat/mov.c index f88c139ba9..6d2f34ce00 100644 --- a/libavformat/mov.c +++ b/libavformat/mov.c @@ -46,6 +46,7 @@ #include "libavutil/opt.h" #include "libavutil/aes.h" #include "libavutil/aes_ctr.h" +#include "libavutil/gain_map.h" #include "libavutil/pixdesc.h" #include "libavutil/sha.h" #include "libavutil/spherical.h" @@ -9539,10 +9540,10 @@ static int mov_read_infe(MOVContext *c, AVIOContext *pb, MOVAtom atom) int64_t size = atom.size; uint32_t item_type; int item_id; - int i, version, ret; + int i, version, flags, ret; version = avio_r8(pb); - avio_rb24(pb); // flags. + flags = avio_rb24(pb); size -= 4; if (size < 0) return AVERROR_INVALIDDATA; @@ -9595,6 +9596,7 @@ static int mov_read_infe(MOVContext *c, AVIOContext *pb, MOVAtom atom) av_bprint_finalize(&item_name, ret ? &item->name : NULL); item->item_id = item_id; item->type = item_type; + item->flags = flags; switch (item_type) { case MKTAG('a','v','0','1'): @@ -9679,13 +9681,104 @@ fail: return ret; } +static int mov_read_iref_dimg_tmap(MOVContext *c, AVIOContext *pb, + HEIFItem *item, int version, int entries) +{ + HEIFGroup *tmap = av_realloc_array(c->heif_group, c->nb_heif_group + 1U, + sizeof(*c->heif_group)); + int ret; + + if (!tmap) + return AVERROR(ENOMEM); + c->heif_group = tmap; + + if (entries != 2) { + avpriv_report_missing_feature(c->fc, "Derived image item of type %s with an " + "item reference count other than 2", + av_fourcc2str(item->type)); + return 0; + } + + tmap = &tmap[c->nb_heif_group]; + + tmap->item_idx_list = NULL; // Not needed + tmap->item_id_list = av_malloc_array(entries, sizeof(*tmap->item_id_list)); + tmap->item_list = av_calloc(entries, sizeof(*tmap->item_list)); + if (!tmap->item_id_list || !tmap->item_list) { + ret = AVERROR(ENOMEM); + goto fail; + } + + /* 'to' item ids */ + for (int i = 0; i < entries; i++) { + tmap->item_id_list[i] = version ? avio_rb32(pb) : avio_rb16(pb); + + if (avio_feof(pb)) { + ret = AVERROR_INVALIDDATA; + goto fail; + } + } + + tmap->nb_items = entries; + tmap->item = item; + ++c->nb_heif_group; + + return 0; +fail: + av_freep(&tmap->item_id_list); + av_freep(&tmap->item_list); + + return ret; +} + +static int mov_read_iref_dimg_grid(MOVContext *c, AVIOContext *pb, + HEIFItem *item, int version, int entries) +{ + HEIFGroup *grid = av_realloc_array(c->heif_group, c->nb_heif_group + 1U, + sizeof(*c->heif_group)); + int ret; + + if (!grid) + return AVERROR(ENOMEM); + c->heif_group = grid; + grid = &grid[c->nb_heif_group]; + + grid->item_id_list = av_malloc_array(entries, sizeof(*grid->item_id_list)); + grid->item_idx_list = av_calloc(entries, sizeof(*grid->item_idx_list)); + grid->item_list = av_calloc(entries, sizeof(*grid->item_list)); + if (!grid->item_id_list || !grid->item_list || !grid->item_idx_list) { + ret = AVERROR(ENOMEM); + goto fail; + } + /* 'to' item ids */ + for (int i = 0; i < entries; i++) { + grid->item_id_list[i] = version ? avio_rb32(pb) : avio_rb16(pb); + + if (avio_feof(pb)) { + ret = AVERROR_INVALIDDATA; + goto fail; + } + } + + grid->nb_items = entries; + grid->item = item; + ++c->nb_heif_group; + + return 0; +fail: + av_freep(&grid->item_id_list); + av_freep(&grid->item_idx_list); + av_freep(&grid->item_list); + + return ret; +} + static int mov_read_iref_dimg(MOVContext *c, AVIOContext *pb, int version) { HEIFItem *item = NULL; - HEIFGroup *grid; - int entries, i; + int entries; int from_item_id = version ? avio_rb32(pb) : avio_rb16(pb); - int ret = 0; + int ret; for (int i = 0; i < c->nb_heif_group; i++) { if (c->heif_group[i].item->item_id == from_item_id) { @@ -9702,6 +9795,7 @@ static int mov_read_iref_dimg(MOVContext *c, AVIOContext *pb, int version) switch (item->type) { case MKTAG('g','r','i','d'): case MKTAG('i','o','v','l'): + case MKTAG('t','m','a','p'): break; default: avpriv_report_missing_feature(c->fc, "Derived Image item of type %s", @@ -9711,7 +9805,7 @@ static int mov_read_iref_dimg(MOVContext *c, AVIOContext *pb, int version) break; } if (!item) { - av_log(c->fc, AV_LOG_ERROR, "Missing grid information\n"); + av_log(c->fc, AV_LOG_ERROR, "Missing derived image information\n"); return AVERROR_INVALIDDATA; } @@ -9722,42 +9816,20 @@ static int mov_read_iref_dimg(MOVContext *c, AVIOContext *pb, int version) return AVERROR_INVALIDDATA; } - grid = av_realloc_array(c->heif_group, c->nb_heif_group + 1U, - sizeof(*c->heif_group)); - if (!grid) - return AVERROR(ENOMEM); - c->heif_group = grid; - grid = &grid[c->nb_heif_group]; - - grid->item_id_list = av_malloc_array(entries, sizeof(*grid->item_id_list)); - grid->item_idx_list = av_calloc(entries, sizeof(*grid->item_idx_list)); - grid->item_list = av_calloc(entries, sizeof(*grid->item_list)); - if (!grid->item_id_list || !grid->item_list || !grid->item_idx_list) { - ret = AVERROR(ENOMEM); - goto fail; - } - /* 'to' item ids */ - for (i = 0; i < entries; i++) { - grid->item_id_list[i] = version ? avio_rb32(pb) : avio_rb16(pb); - - if (avio_feof(pb)) { - ret = AVERROR_INVALIDDATA; - goto fail; - } - } - - grid->nb_items = entries; - grid->item = item; - ++c->nb_heif_group; - av_log(c->fc, AV_LOG_TRACE, "dimg: from_item_id %d, entries %d\n", from_item_id, entries); - return 0; -fail: - av_freep(&grid->item_id_list); - av_freep(&grid->item_idx_list); - av_freep(&grid->item_list); + switch (item->type) { + case MKTAG('g','r','i','d'): + case MKTAG('i','o','v','l'): + ret = mov_read_iref_dimg_grid(c, pb, item, version, entries); + break; + case MKTAG('t','m','a','p'): + ret = mov_read_iref_dimg_tmap(c, pb, item, version, entries); + break; + default: + av_unreachable("item->type should have been checked above"); + } return ret; } @@ -11332,6 +11404,203 @@ static int mov_parse_tiles(AVFormatContext *s) return 0; } +static int mov_parse_heif_layered_video_tmap(AVFormatContext *s, const HEIFGroup *tmap) +{ + MOVContext *mov = s->priv_data; + AVStreamGroup *stg; + AVStreamGroupLayeredVideo *layered_video; + AVGainMapParams *gain = NULL; + uint8_t *payload = NULL; + int64_t pos, offset = 0; + size_t gain_size; + int64_t err64; + int err; + + if (!(s->pb->seekable & AVIO_SEEKABLE_NORMAL)) { + av_log(s, AV_LOG_WARNING, "tmap box with non seekable input\n"); + return 0; + } + + stg = avformat_stream_group_create(s, AV_STREAM_GROUP_PARAMS_GAIN_MAP, NULL); + if (!stg) + return AVERROR(ENOMEM); + + stg->id = tmap->item->item_id; + layered_video = stg->params.layered_video; + + for (int j = 0; j < tmap->nb_items; j++) { + int item_id = tmap->item_id_list[j]; + HEIFItem *item = get_heif_item(mov, item_id); + AVStreamGroup *child_stg; + AVStream *st; + + if (!item) { + av_log(s, AV_LOG_WARNING, "HEIF item id %d referenced by tmap id %d doesn't " + "exist\n", + item_id, tmap->item->item_id); + ff_remove_stream_group(s, stg); + return 0; + } + + st = item->st; + child_stg = item->stg; + if (!st && !child_stg) { + av_log(s, AV_LOG_WARNING, "HEIF item id %d from tmap id %d doesn't " + "reference a stream or tmap\n", + item_id, tmap->item->item_id); + ff_remove_stream_group(s, stg); + return 0; + } + av_assert0(!st != !child_stg); + + tmap->item_list[j] = item; + + if (st) + err = avformat_stream_group_add_stream(stg, st); + else + err = avformat_stream_group_add_stream_group(stg, child_stg); + if (err < 0) { + if (err == AVERROR(EEXIST)) + err = AVERROR_INVALIDDATA; + + return err; + } + + if ((item->flags & MOV_INFE_FLAG_HIDDEN)) { + if (st) + st->disposition |= AV_DISPOSITION_DEPENDENT; + else + child_stg->disposition |= AV_DISPOSITION_DEPENDENT; + } + } + + layered_video->width = tmap->item->width; + layered_video->height = tmap->item->height; + + if (tmap->item->is_idat_relative) { + if (!mov->idat_offset) { + av_log(s, AV_LOG_ERROR, "Missing idat box required by the tmap\n"); + return AVERROR_INVALIDDATA; + } + offset = mov->idat_offset; + } + + if (offset > INT64_MAX - tmap->item->extent_offset) + return AVERROR_INVALIDDATA; + + gain = av_gain_map_params_alloc(&gain_size); + if (!gain) + return AVERROR(ENOMEM); + + payload = av_malloc(tmap->item->extent_length); + if (!payload) { + return AVERROR(ENOMEM); + goto fail; + } + + pos = avio_tell(s->pb); + err64 = avio_seek(s->pb, tmap->item->extent_offset + offset, SEEK_SET); + if (err64 != tmap->item->extent_offset + offset) { + err64 = err64 < 0 ? err64 : AVERROR_INVALIDDATA; + goto fail; + } + avio_r8(s->pb); /* version */ + + err = avio_read(s->pb, payload, tmap->item->extent_length); + if (err == tmap->item->extent_length) + err64 = avio_seek(s->pb, pos, SEEK_SET); + else { + err = err < 0 ? err : AVERROR_INVALIDDATA; + goto fail; + } + if (err64 != pos) { + err = err64 < 0 ? err64 : AVERROR_INVALIDDATA; + goto fail; + } + + err = av_gain_map_params_from_iso21496(gain, payload, tmap->item->extent_length); + av_freep(&payload); + if (err < 0) + goto fail; + + for (int j = 0; j < tmap->nb_items; j++) { + HEIFItem *item = tmap->item_list[j]; + AVPacketSideData *sd; + AVPacketSideData **coded_side_data; + int *nb_coded_side_data; + + if (!(item->flags & MOV_INFE_FLAG_HIDDEN)) + continue; + + layered_video->el_index = j; + + if (item->st) { + coded_side_data = &item->st->codecpar->coded_side_data; + nb_coded_side_data = &item->st->codecpar->nb_coded_side_data; + } else { + av_assert0(item->stg); + coded_side_data = &item->stg->coded_side_data; + nb_coded_side_data = &item->stg->nb_coded_side_data; + } + + sd = av_packet_side_data_add(coded_side_data, nb_coded_side_data, + AV_PKT_DATA_GAIN_MAP_PARAMS, + (uint8_t *)gain, gain_size, 0); + if (!sd) { + av_free(gain); + return AVERROR(ENOMEM); + } + gain = NULL; + + break; + } + av_free(gain); + + err = mov_parse_heif_item_references(s, tmap->item, &stg->coded_side_data, + &stg->nb_coded_side_data); + if (err < 0) + return err; + + if (tmap->item->name) + av_dict_set(&stg->metadata, "title", tmap->item->name, 0); + if (tmap->item->item_id == mov->primary_item_id) + stg->disposition |= AV_DISPOSITION_DEFAULT; + + tmap->item->stg = stg; + + return 0; +fail: + av_free(gain); + av_free(payload); + + return err; +} + +static int mov_parse_heif_layered_video(AVFormatContext *s) +{ + MOVContext *mov = s->priv_data; + int err; + + for (int i = 0; i < mov->nb_heif_group; i++) { + const HEIFGroup *tmap = &mov->heif_group[i]; + + if (!tmap->item) + continue; + + switch (tmap->item->type) { + case MKTAG('t','m','a','p'): + err = mov_parse_heif_layered_video_tmap(s, tmap); + if (err < 0) + return err; + break; + default: + break; + } + } + + return 0; +} + static int mov_parse_heif_items(AVFormatContext *s) { MOVContext *mov = s->priv_data; @@ -11390,6 +11659,10 @@ static int mov_parse_heif_items(AVFormatContext *s) err = mov_parse_tiles(s); if (err < 0) return err; + + err = mov_parse_heif_layered_video(s); + if (err < 0) + return err; } return 0; -- To stop receiving notification emails like this one, please contact [email protected]. _______________________________________________ ffmpeg-cvslog mailing list -- [email protected] To unsubscribe send an email to [email protected]
