diff --git a/libavcodec/exif.c b/libavcodec/exif.c index 38438661cc..0de543e35a 100644 --- a/libavcodec/exif.c +++ b/libavcodec/exif.c @@ -192,6 +192,24 @@ static const struct exif_tag tag_list[] = { // JEITA CP-3451 EXIF specification: {"InteropIFD", 0xA005}, // <- Table 13 Interoperability IFD Attribute Information {"GlobalParametersIFD", 0x0190}, {"ProfileIFD", 0xc6f5}, + + /* Extra FFmpeg tags */ + { "IFD1", 0xFFFC}, + { "IFD2", 0xFFFB}, + { "IFD3", 0xFFFA}, + { "IFD4", 0xFFF9}, + { "IFD5", 0xFFF8}, + { "IFD6", 0xFFF7}, + { "IFD7", 0xFFF6}, + { "IFD8", 0xFFF5}, + { "IFD9", 0xFFF4}, + { "IFD10", 0xFFF3}, + { "IFD11", 0xFFF2}, + { "IFD12", 0xFFF1}, + { "IFD13", 0xFFF0}, + { "IFD14", 0xFFEF}, + { "IFD15", 0xFFEE}, + { "IFD16", 0xFFED}, }; /* same as type_sizes but with string == 1 */ @@ -655,7 +673,9 @@ static size_t exif_get_ifd_size(const AVExifMetadata *ifd) for (size_t i = 0; i < ifd->count; i++) { const AVExifEntry *entry = &ifd->entries[i]; if (entry->type == AV_TIFF_IFD) { - total_size += BASE_TAG_SIZE + exif_get_ifd_size(&entry->value.ifd) + entry->ifd_offset; + /* this is an extra IFD, not an entry, so we don't need to add base tag size */ + size_t base_size = entry->id > 0xFFECu && entry->id <= 0xFFFCu ? 0 : BASE_TAG_SIZE; + total_size += base_size + exif_get_ifd_size(&entry->value.ifd) + entry->ifd_offset; } else { size_t payload_size = entry->count * exif_sizes[entry->type]; total_size += BASE_TAG_SIZE + (payload_size > 4 ? payload_size : 0); @@ -728,12 +748,16 @@ int av_exif_write(void *logctx, const AVExifMetadata *ifd, AVBufferRef **buffer, AVBufferRef *buf = NULL; size_t size, headsize = 8; PutByteContext pb; - int ret, off = 0; + int ret = 0, off = 0, next; + AVExifMetadata *ifd_new = NULL; + AVExifMetadata extra_ifds[16] = { 0 }; int le = 1; - if (*buffer) - return AVERROR(EINVAL); + if (*buffer) { + ret = AVERROR(EINVAL); + goto end; + } size = exif_get_ifd_size(ifd); switch (header_mode) { @@ -753,8 +777,10 @@ int av_exif_write(void *logctx, const AVExifMetadata *ifd, AVBufferRef **buffer, break; } buf = av_buffer_alloc(size + off + headsize); - if (!buf) - return AVERROR(ENOMEM); + if (!buf) { + ret = AVERROR(ENOMEM); + goto end; + } if (header_mode == AV_EXIF_EXIF00) { AV_WL32(buf->data, MKTAG('E','x','i','f')); @@ -772,16 +798,63 @@ int av_exif_write(void *logctx, const AVExifMetadata *ifd, AVBufferRef **buffer, tput32(&pb, le, 8); } + int extras; + for (extras = 0; extras < FF_ARRAY_ELEMS(extra_ifds); extras++) { + AVExifEntry *extra_entry = NULL; + uint16_t extra_tag = 0xFFFCu - extras; + ret = av_exif_get_entry(logctx, (AVExifMetadata *) ifd, extra_tag, 0, &extra_entry); + if (ret <= 0) + break; + av_log(logctx, AV_LOG_DEBUG, "found extra IFD tag: %04x\n", extra_tag); + if (!ifd_new) { + ifd_new = av_exif_clone_ifd(ifd); + if (!ifd_new) + break; + ifd = ifd_new; + } + /* calling remove_entry will call av_exif_free on the original */ + AVExifMetadata *cloned = av_exif_clone_ifd(&extra_entry->value.ifd); + if (!cloned) + break; + extra_ifds[extras] = *cloned; + /* don't use av_exif_free here, we want to preserve internals */ + av_free(cloned); + ret = av_exif_remove_entry(logctx, ifd_new, extra_tag, 0); + if (ret < 0) + break; + } + + next = bytestream2_tell_p(&pb); ret = exif_write_ifd(logctx, &pb, le, 0, ifd); if (ret < 0) { av_buffer_unref(&buf); av_log(logctx, AV_LOG_ERROR, "error writing EXIF data: %s\n", av_err2str(ret)); return ret; } + next += ret; + + for (int i = 0; i < extras; i++) { + av_log(logctx, AV_LOG_DEBUG, "writing additional ifd at: %d\n", next); + /* exif_write_ifd always writes 0 i.e. last ifd so we overwrite that here */ + bytestream2_seek_p(&pb, -4, SEEK_CUR); + tput32(&pb, le, next); + bytestream2_seek_p(&pb, next, SEEK_SET); + ret = exif_write_ifd(logctx, &pb, le, 0, &extra_ifds[i]); + if (ret < 0) + break; + next += ret; + } *buffer = buf; + ret = 0; - return 0; +end: + av_exif_free(ifd_new); + av_freep(&ifd_new); + for (int i = 0; i < FF_ARRAY_ELEMS(extra_ifds); i++) + av_exif_free(&extra_ifds[i]); + + return ret; } int av_exif_parse_buffer(void *logctx, const uint8_t *buf, size_t size, @@ -839,8 +912,30 @@ int av_exif_parse_buffer(void *logctx, const uint8_t *buf, size_t size, av_log(logctx, AV_LOG_ERROR, "error decoding EXIF data: %s\n", av_err2str(ret)); return ret; } + if (!ret) + goto finish; + int next = ret; + bytestream2_seek(&gbytes, next, SEEK_SET); - return bytestream2_tell(&gbytes); + /* cap at 16 extra IFDs for sanity/parse security */ + for (int extra_tag = 0xFFFCu; extra_tag > 0xFFECu; extra_tag--) { + AVExifMetadata extra_ifd = { 0 }; + ret = exif_parse_ifd_list(logctx, &gbytes, le, 0, &extra_ifd, 1); + if (ret < 0) { + av_exif_free(&extra_ifd); + break; + } + next = ret; + av_log(logctx, AV_LOG_DEBUG, "found extra IFD: %04x with next=%d\n", extra_tag, ret); + bytestream2_seek(&gbytes, next, SEEK_SET); + ret = av_exif_set_entry(logctx, ifd, extra_tag, AV_TIFF_IFD, 1, NULL, 0, &extra_ifd); + av_exif_free(&extra_ifd); + if (ret < 0 || !next || bytestream2_get_bytes_left(&gbytes) <= 0) + break; + } + +finish: + return bytestream2_tell(&gbytes) + off; } #define COLUMN_SEP(i, c) ((i) ? ((i) % (c) ? ", " : "\n") : "") diff --git a/tests/ref/fate/exif-image-jpg b/tests/ref/fate/exif-image-jpg index 84136a9f85..f1a5ef00e8 100644 --- a/tests/ref/fate/exif-image-jpg +++ b/tests/ref/fate/exif-image-jpg @@ -197,6 +197,12 @@ TAG:ExifIFD/ExposureMode= 0 TAG:ExifIFD/WhiteBalance= 0 TAG:ExifIFD/DigitalZoomRatio= 4000:4000 TAG:ExifIFD/SceneCaptureType= 0 +TAG:IFD1/Compression= 6 +TAG:IFD1/XResolution= 180:1 +TAG:IFD1/YResolution= 180:1 +TAG:IFD1/ResolutionUnit= 2 +TAG:IFD1/JPEGInterchangeFormat= 5108 +TAG:IFD1/JPEGInterchangeFormatLength= 4214 [SIDE_DATA] side_data_type=3x3 displaymatrix displaymatrix= @@ -208,6 +214,6 @@ rotation=0 [/SIDE_DATA] [SIDE_DATA] side_data_type=EXIF metadata -size=3285 +size=3379 [/SIDE_DATA] [/FRAME] diff --git a/tests/ref/fate/exif-image-webp b/tests/ref/fate/exif-image-webp index 7b695bc923..27b062d948 100644 --- a/tests/ref/fate/exif-image-webp +++ b/tests/ref/fate/exif-image-webp @@ -197,6 +197,12 @@ TAG:ExifIFD/ExposureMode= 0 TAG:ExifIFD/WhiteBalance= 0 TAG:ExifIFD/DigitalZoomRatio= 4000:4000 TAG:ExifIFD/SceneCaptureType= 0 +TAG:IFD1/Compression= 6 +TAG:IFD1/XResolution= 180:1 +TAG:IFD1/YResolution= 180:1 +TAG:IFD1/ResolutionUnit= 2 +TAG:IFD1/JPEGInterchangeFormat= 5108 +TAG:IFD1/JPEGInterchangeFormatLength= 4214 [SIDE_DATA] side_data_type=3x3 displaymatrix displaymatrix= @@ -208,6 +214,6 @@ rotation=0 [/SIDE_DATA] [SIDE_DATA] side_data_type=EXIF metadata -size=3285 +size=3379 [/SIDE_DATA] [/FRAME] diff --git a/tests/ref/fate/mov-heic-demux-still-image-multiple-thumb b/tests/ref/fate/mov-heic-demux-still-image-multiple-thumb index 4e66fe978c..628976644e 100644 --- a/tests/ref/fate/mov-heic-demux-still-image-multiple-thumb +++ b/tests/ref/fate/mov-heic-demux-still-image-multiple-thumb @@ -1895,9 +1895,27 @@ TAG:ExifIFD/0xA434=LUMIX S 24-60/F2.8 TAG:0x9006= 39936 TAG:0x9007= 65536 TAG:0x9008= 272401 +TAG:IFD1/Compression= 6 +TAG:IFD1/StripOffsets= 106496 +TAG:IFD1/Orientation= 1 +TAG:IFD1/XResolution= 180:1 +TAG:IFD1/YResolution= 180:1 +TAG:IFD1/ResolutionUnit= 2 +TAG:IFD1/JPEGInterchangeFormat= 106496 +TAG:IFD1/JPEGInterchangeFormatLength= 20480 +TAG:IFD1/YCbCrPositioning= 2 +TAG:IFD2/Compression= 6 +TAG:IFD2/StripOffsets= 126976 +TAG:IFD2/Orientation= 1 +TAG:IFD2/XResolution= 180:1 +TAG:IFD2/YResolution= 180:1 +TAG:IFD2/ResolutionUnit= 2 +TAG:IFD2/JPEGInterchangeFormat= 126976 +TAG:IFD2/JPEGInterchangeFormatLength= 858544 +TAG:IFD2/YCbCrPositioning= 2 [SIDE_DATA] side_data_type=EXIF metadata -size=30308 +size=30568 [/SIDE_DATA] [SIDE_DATA] side_data_type=H.26[45] User Data Unregistered SEI message @@ -1927,7 +1945,7 @@ DISPOSITION:still_image=0 DISPOSITION:multilayer=0 [SIDE_DATA] side_data_type=EXIF metadata -size=30308 +size=30568 [/SIDE_DATA] [/STREAM] [STREAM]