1
0
mirror of https://github.com/FFmpeg/FFmpeg.git synced 2025-09-16 08:36:51 +02:00

avformat/mov: export Exif metadata from HEIF streams

Signed-off-by: James Almer <jamrial@gmail.com>
This commit is contained in:
James Almer
2025-08-20 19:53:53 -03:00
parent 74e430202d
commit 310be1c5a4
4 changed files with 127 additions and 8 deletions

View File

@@ -51,6 +51,7 @@
#include "libavutil/timecode.h"
#include "libavutil/uuid.h"
#include "libavcodec/ac3tab.h"
#include "libavcodec/exif.h"
#include "libavcodec/flac.h"
#include "libavcodec/hevc/hevc.h"
#include "libavcodec/mpegaudiodecheader.h"
@@ -9085,12 +9086,12 @@ static int mov_read_iref_dimg(MOVContext *c, AVIOContext *pb, int version)
return 0;
}
static int mov_read_iref_thmb(MOVContext *c, AVIOContext *pb, int version)
static int mov_read_iref_cdsc(MOVContext *c, AVIOContext *pb, uint32_t type, int version)
{
HEIFItem *from_item = NULL;
int entries;
int from_item_id = version ? avio_rb32(pb) : avio_rb16(pb);
const HEIFItemRef ref = { MKTAG('t','h','m','b'), from_item_id };
const HEIFItemRef ref = { type, from_item_id };
from_item = get_heif_item(c, from_item_id);
if (!from_item) {
@@ -9103,7 +9104,8 @@ static int mov_read_iref_thmb(MOVContext *c, AVIOContext *pb, int version)
for (int i = 0; i < entries; i++) {
HEIFItem *item = get_heif_item(c, version ? avio_rb32(pb) : avio_rb16(pb));
if (!item) {
av_log(c->fc, AV_LOG_WARNING, "Missing stream referenced by thmb item\n");
av_log(c->fc, AV_LOG_WARNING, "Missing stream referenced by %s item\n",
av_fourcc2str(type));
continue;
}
@@ -9112,8 +9114,8 @@ static int mov_read_iref_thmb(MOVContext *c, AVIOContext *pb, int version)
return AVERROR(ENOMEM);
}
av_log(c->fc, AV_LOG_TRACE, "thmb: from_item_id %d, entries %d\n",
from_item_id, entries);
av_log(c->fc, AV_LOG_TRACE, "%s: from_item_id %d, entries %d\n",
av_fourcc2str(type), from_item_id, entries);
return 0;
}
@@ -9142,8 +9144,9 @@ static int mov_read_iref(MOVContext *c, AVIOContext *pb, MOVAtom atom)
case MKTAG('d','i','m','g'):
mov_read_iref_dimg(c, pb, version);
break;
case MKTAG('c','d','s','c'):
case MKTAG('t','h','m','b'):
mov_read_iref_thmb(c, pb, version);
mov_read_iref_cdsc(c, pb, type, version);
break;
default:
av_log(c->fc, AV_LOG_DEBUG, "Unknown iref type %s size %"PRIu32"\n",
@@ -10308,6 +10311,86 @@ fail:
return ret;
}
static int mov_parse_exif_item(AVFormatContext *s,
AVPacketSideData **coded_side_data, int *nb_coded_side_data,
const HEIFItem *ref)
{
MOVContext *c = s->priv_data;
AVPacketSideData *sd;
AVExifMetadata ifd = { 0 };
AVExifEntry *entry = NULL;
AVBufferRef *buf;
int64_t offset = 0, pos = avio_tell(s->pb);
unsigned orientation_id = av_exif_get_tag_id("Orientation");
int err;
if (!(s->pb->seekable & AVIO_SEEKABLE_NORMAL)) {
av_log(c->fc, AV_LOG_WARNING, "Exif metadata with non seekable input\n");
return AVERROR_PATCHWELCOME;
}
if (ref->is_idat_relative) {
if (!c->idat_offset) {
av_log(c->fc, AV_LOG_ERROR, "missing idat box required by the Exif metadata\n");
return AVERROR_INVALIDDATA;
}
offset = c->idat_offset;
}
buf = av_buffer_alloc(ref->extent_length);
if (!buf)
return AVERROR(ENOMEM);
avio_seek(s->pb, ref->extent_offset + offset, SEEK_SET);
err = avio_read(s->pb, buf->data, ref->extent_length);
if (err != ref->extent_length) {
if (err > 0)
err = AVERROR_INVALIDDATA;
goto fail;
}
// HEIF spec states that Exif metadata is informative. The irot item property is
// the normative source of rotation information. So we remove any Orientation tag
// present in the Exif buffer.
err = av_exif_parse_buffer(s, buf->data, ref->extent_length, &ifd, AV_EXIF_T_OFF);
if (err < 0) {
av_log(s, AV_LOG_ERROR, "Unable to parse Exif metadata\n");
goto fail;
}
err = av_exif_remove_entry(s, &ifd, orientation_id, 0);
if (err < 0)
goto fail;
else if (!err)
goto finish;
av_buffer_unref(&buf);
err = av_exif_write(s, &ifd, &buf, AV_EXIF_T_OFF);
if (err < 0)
goto fail;
finish:
offset = AV_RB32(buf->data) + 4;
if (offset >= buf->size) {
err = AVERROR_INVALIDDATA;
goto fail;
}
sd = av_packet_side_data_new(coded_side_data, nb_coded_side_data,
AV_PKT_DATA_EXIF, buf->size - offset, 0);
if (!sd) {
err = AVERROR(ENOMEM);
goto fail;
}
memcpy(sd->data, buf->data + offset, buf->size - offset);
err = 0;
fail:
av_buffer_unref(&buf);
av_exif_free(&ifd);
avio_seek(s->pb, pos, SEEK_SET);
return err;
}
static int mov_parse_tiles(AVFormatContext *s)
{
MOVContext *mov = s->priv_data;
@@ -10393,6 +10476,22 @@ static int mov_parse_tiles(AVFormatContext *s)
if (err < 0)
return err;
for (int j = 0; j < grid->item->nb_iref_list; j++) {
HEIFItem *ref = get_heif_item(mov, grid->item->iref_list[j].item_id);
av_assert0(ref);
switch(ref->type) {
case MKTAG('E','x','i','f'):
err = mov_parse_exif_item(s, &tile_grid->coded_side_data,
&tile_grid->nb_coded_side_data, ref);
if (err < 0 && (s->error_recognition & AV_EF_EXPLODE))
return err;
break;
default:
break;
}
}
/* rotation */
if (grid->item->rotation || grid->item->hflip || grid->item->vflip) {
err = set_display_matrix_from_item(&tile_grid->coded_side_data,
@@ -10459,6 +10558,22 @@ static int mov_parse_heif_items(AVFormatContext *s)
if (item->item_id == mov->primary_item_id)
st->disposition |= AV_DISPOSITION_DEFAULT;
for (int j = 0; j < item->nb_iref_list; j++) {
HEIFItem *ref = get_heif_item(mov, item->iref_list[j].item_id);
av_assert0(ref);
switch(ref->type) {
case MKTAG('E','x','i','f'):
err = mov_parse_exif_item(s, &st->codecpar->coded_side_data,
&st->codecpar->nb_coded_side_data, ref);
if (err < 0 && (s->error_recognition & AV_EF_EXPLODE))
return err;
break;
default:
break;
}
}
if (item->rotation || item->hflip || item->vflip) {
err = set_display_matrix_from_item(&st->codecpar->coded_side_data,
&st->codecpar->nb_coded_side_data, item);

View File

@@ -32,7 +32,7 @@
#include "version_major.h"
#define LIBAVFORMAT_VERSION_MINOR 4
#define LIBAVFORMAT_VERSION_MICRO 101
#define LIBAVFORMAT_VERSION_MICRO 102
#define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
LIBAVFORMAT_VERSION_MINOR, \

View File

@@ -188,7 +188,7 @@ fate-mov-heic-demux-clap-irot-imir: CMD = stream_demux mov $(TARGET_SAMPLES)/hei
FATE_MOV_FFMPEG_FFPROBE_SAMPLES-$(call FRAMECRC, MOV, HEVC MJPEG, HEVC_PARSER) \
+= fate-mov-heic-demux-still-image-multiple-thumb
fate-mov-heic-demux-still-image-multiple-thumb: CMD = stream_demux mov $(TARGET_SAMPLES)/heif/P1001091.HIF "" "-c:v copy -map 0" \
"-show_entries stream=index,id:stream_disposition"
"-show_entries stream=index,id:stream_disposition:stream_side_data_list"
# heic demuxing - still image with multiple items in a grid.
FATE_MOV_FFMPEG_FFPROBE_SAMPLES-$(call FRAMECRC, MOV, HEVC, HEVC_PARSER) \

View File

@@ -39,6 +39,10 @@ DISPOSITION:metadata=0
DISPOSITION:dependent=0
DISPOSITION:still_image=0
DISPOSITION:multilayer=0
[SIDE_DATA]
side_data_type=EXIF metadata
size=30308
[/SIDE_DATA]
[/STREAM]
[STREAM]
index=1