Files
FFmpeg/libavcodec/exif.c
Leo Izen ec42a0aec0 avcodec/exif: avoid overflow with supplemental extra IFDs
If extra IFDs occur outside of the top level, we don't allocate enough
buffer space for them. This commit changes our calculation to include
their size always, and then we shrink the allocated buffer once every
IFD is written (by at most 192 bytes).

Signed-off-by: Leo Izen <leo.izen@gmail.com>
2026-01-07 04:01:51 -05:00

1533 lines
50 KiB
C

/*
* EXIF metadata parser
* Copyright (c) 2013 Thilo Borgmann <thilo.borgmann _at_ mail.de>
* Copyright (c) 2024-2025 Leo Izen <leo.izen@gmail.com>
*
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* FFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* @file
* EXIF metadata parser
* @author Thilo Borgmann <thilo.borgmann _at_ mail.de>
* @author Leo Izen <leo.izen@gmail.com>
*/
#include <inttypes.h>
#include "libavutil/avconfig.h"
#include "libavutil/bprint.h"
#include "libavutil/display.h"
#include "libavutil/intreadwrite.h"
#include "libavutil/mem.h"
#include "bytestream.h"
#include "exif_internal.h"
#include "tiff_common.h"
#define EXIF_II_LONG 0x49492a00
#define EXIF_MM_LONG 0x4d4d002a
#define BASE_TAG_SIZE 12
#define IFD_EXTRA_SIZE 6
#define EXIF_TAG_NAME_LENGTH 32
#define MAKERNOTE_TAG 0x927c
#define ORIENTATION_TAG 0x112
#define EXIFIFD_TAG 0x8769
#define IMAGE_WIDTH_TAG 0x100
#define IMAGE_LENGTH_TAG 0x101
#define PIXEL_X_TAG 0xa002
#define PIXEL_Y_TAG 0xa003
struct exif_tag {
const char name[EXIF_TAG_NAME_LENGTH];
uint16_t id;
};
static const struct exif_tag tag_list[] = { // JEITA CP-3451 EXIF specification:
{"GPSVersionID", 0x00}, // <- Table 12 GPS Attribute Information
{"GPSLatitudeRef", 0x01},
{"GPSLatitude", 0x02},
{"GPSLongitudeRef", 0x03},
{"GPSLongitude", 0x04},
{"GPSAltitudeRef", 0x05},
{"GPSAltitude", 0x06},
{"GPSTimeStamp", 0x07},
{"GPSSatellites", 0x08},
{"GPSStatus", 0x09},
{"GPSMeasureMode", 0x0A},
{"GPSDOP", 0x0B},
{"GPSSpeedRef", 0x0C},
{"GPSSpeed", 0x0D},
{"GPSTrackRef", 0x0E},
{"GPSTrack", 0x0F},
{"GPSImgDirectionRef", 0x10},
{"GPSImgDirection", 0x11},
{"GPSMapDatum", 0x12},
{"GPSDestLatitudeRef", 0x13},
{"GPSDestLatitude", 0x14},
{"GPSDestLongitudeRef", 0x15},
{"GPSDestLongitude", 0x16},
{"GPSDestBearingRef", 0x17},
{"GPSDestBearing", 0x18},
{"GPSDestDistanceRef", 0x19},
{"GPSDestDistance", 0x1A},
{"GPSProcessingMethod", 0x1B},
{"GPSAreaInformation", 0x1C},
{"GPSDateStamp", 0x1D},
{"GPSDifferential", 0x1E},
{"ImageWidth", 0x100}, // <- Table 3 TIFF Rev. 6.0 Attribute Information Used in Exif
{"ImageLength", 0x101},
{"BitsPerSample", 0x102},
{"Compression", 0x103},
{"PhotometricInterpretation", 0x106},
{"Orientation", 0x112},
{"SamplesPerPixel", 0x115},
{"PlanarConfiguration", 0x11C},
{"YCbCrSubSampling", 0x212},
{"YCbCrPositioning", 0x213},
{"XResolution", 0x11A},
{"YResolution", 0x11B},
{"ResolutionUnit", 0x128},
{"StripOffsets", 0x111},
{"RowsPerStrip", 0x116},
{"StripByteCounts", 0x117},
{"JPEGInterchangeFormat", 0x201},
{"JPEGInterchangeFormatLength",0x202},
{"TransferFunction", 0x12D},
{"WhitePoint", 0x13E},
{"PrimaryChromaticities", 0x13F},
{"YCbCrCoefficients", 0x211},
{"ReferenceBlackWhite", 0x214},
{"DateTime", 0x132},
{"ImageDescription", 0x10E},
{"Make", 0x10F},
{"Model", 0x110},
{"Software", 0x131},
{"Artist", 0x13B},
{"Copyright", 0x8298},
{"ExifVersion", 0x9000}, // <- Table 4 Exif IFD Attribute Information (1)
{"FlashpixVersion", 0xA000},
{"ColorSpace", 0xA001},
{"ComponentsConfiguration", 0x9101},
{"CompressedBitsPerPixel", 0x9102},
{"PixelXDimension", 0xA002},
{"PixelYDimension", 0xA003},
{"MakerNote", 0x927C},
{"UserComment", 0x9286},
{"RelatedSoundFile", 0xA004},
{"DateTimeOriginal", 0x9003},
{"DateTimeDigitized", 0x9004},
{"SubSecTime", 0x9290},
{"SubSecTimeOriginal", 0x9291},
{"SubSecTimeDigitized", 0x9292},
{"ImageUniqueID", 0xA420},
{"ExposureTime", 0x829A}, // <- Table 5 Exif IFD Attribute Information (2)
{"FNumber", 0x829D},
{"ExposureProgram", 0x8822},
{"SpectralSensitivity", 0x8824},
{"ISOSpeedRatings", 0x8827},
{"OECF", 0x8828},
{"ShutterSpeedValue", 0x9201},
{"ApertureValue", 0x9202},
{"BrightnessValue", 0x9203},
{"ExposureBiasValue", 0x9204},
{"MaxApertureValue", 0x9205},
{"SubjectDistance", 0x9206},
{"MeteringMode", 0x9207},
{"LightSource", 0x9208},
{"Flash", 0x9209},
{"FocalLength", 0x920A},
{"SubjectArea", 0x9214},
{"FlashEnergy", 0xA20B},
{"SpatialFrequencyResponse", 0xA20C},
{"FocalPlaneXResolution", 0xA20E},
{"FocalPlaneYResolution", 0xA20F},
{"FocalPlaneResolutionUnit", 0xA210},
{"SubjectLocation", 0xA214},
{"ExposureIndex", 0xA215},
{"SensingMethod", 0xA217},
{"FileSource", 0xA300},
{"SceneType", 0xA301},
{"CFAPattern", 0xA302},
{"CustomRendered", 0xA401},
{"ExposureMode", 0xA402},
{"WhiteBalance", 0xA403},
{"DigitalZoomRatio", 0xA404},
{"FocalLengthIn35mmFilm", 0xA405},
{"SceneCaptureType", 0xA406},
{"GainControl", 0xA407},
{"Contrast", 0xA408},
{"Saturation", 0xA409},
{"Sharpness", 0xA40A},
{"DeviceSettingDescription", 0xA40B},
{"SubjectDistanceRange", 0xA40C},
/* InteropIFD tags */
{"RelatedImageFileFormat", 0x1000},
{"RelatedImageWidth", 0x1001},
{"RelatedImageLength", 0x1002},
/* private EXIF tags */
{"PrintImageMatching", 0xC4A5}, // <- undocumented meaning
/* IFD tags */
{"ExifIFD", 0x8769}, // <- An IFD pointing to standard Exif metadata
{"GPSInfo", 0x8825}, // <- An IFD pointing to GPS Exif Metadata
{"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 */
static const size_t exif_sizes[] = {
[0] = 0,
[AV_TIFF_BYTE] = 1,
[AV_TIFF_STRING] = 1,
[AV_TIFF_SHORT] = 2,
[AV_TIFF_LONG] = 4,
[AV_TIFF_RATIONAL] = 8,
[AV_TIFF_SBYTE] = 1,
[AV_TIFF_UNDEFINED] = 1,
[AV_TIFF_SSHORT] = 2,
[AV_TIFF_SLONG] = 4,
[AV_TIFF_SRATIONAL] = 8,
[AV_TIFF_FLOAT] = 4,
[AV_TIFF_DOUBLE] = 8,
[AV_TIFF_IFD] = 4,
};
const char *av_exif_get_tag_name(uint16_t id)
{
for (size_t i = 0; i < FF_ARRAY_ELEMS(tag_list); i++) {
if (tag_list[i].id == id)
return tag_list[i].name;
}
return NULL;
}
int32_t av_exif_get_tag_id(const char *name)
{
if (!name)
return -1;
for (size_t i = 0; i < FF_ARRAY_ELEMS(tag_list); i++) {
if (!strcmp(tag_list[i].name, name))
return tag_list[i].id;
}
return -1;
}
static inline void tput16(PutByteContext *pb, const int le, const uint16_t value)
{
le ? bytestream2_put_le16(pb, value) : bytestream2_put_be16(pb, value);
}
static inline void tput32(PutByteContext *pb, const int le, const uint32_t value)
{
le ? bytestream2_put_le32(pb, value) : bytestream2_put_be32(pb, value);
}
static inline void tput64(PutByteContext *pb, const int le, const uint64_t value)
{
le ? bytestream2_put_le64(pb, value) : bytestream2_put_be64(pb, value);
}
static int exif_read_values(void *logctx, GetByteContext *gb, int le, AVExifEntry *entry)
{
switch (entry->type) {
case AV_TIFF_SHORT:
case AV_TIFF_LONG:
entry->value.uint = av_calloc(entry->count, sizeof(*entry->value.uint));
break;
case AV_TIFF_SSHORT:
case AV_TIFF_SLONG:
entry->value.sint = av_calloc(entry->count, sizeof(*entry->value.sint));
break;
case AV_TIFF_DOUBLE:
case AV_TIFF_FLOAT:
entry->value.dbl = av_calloc(entry->count, sizeof(*entry->value.dbl));
break;
case AV_TIFF_RATIONAL:
case AV_TIFF_SRATIONAL:
entry->value.rat = av_calloc(entry->count, sizeof(*entry->value.rat));
break;
case AV_TIFF_UNDEFINED:
case AV_TIFF_BYTE:
entry->value.ubytes = av_mallocz(entry->count);
break;
case AV_TIFF_SBYTE:
entry->value.sbytes = av_mallocz(entry->count);
break;
case AV_TIFF_STRING:
entry->value.str = av_mallocz(entry->count + 1);
break;
case AV_TIFF_IFD:
av_log(logctx, AV_LOG_WARNING, "Bad IFD type for non-IFD tag\n");
return AVERROR_INVALIDDATA;
}
if (!entry->value.ptr)
return AVERROR(ENOMEM);
switch (entry->type) {
case AV_TIFF_SHORT:
for (size_t i = 0; i < entry->count; i++)
entry->value.uint[i] = ff_tget_short(gb, le);
break;
case AV_TIFF_LONG:
for (size_t i = 0; i < entry->count; i++)
entry->value.uint[i] = ff_tget_long(gb, le);
break;
case AV_TIFF_SSHORT:
for (size_t i = 0; i < entry->count; i++)
entry->value.sint[i] = (int16_t) ff_tget_short(gb, le);
break;
case AV_TIFF_SLONG:
for (size_t i = 0; i < entry->count; i++)
entry->value.sint[i] = (int32_t) ff_tget_long(gb, le);
break;
case AV_TIFF_DOUBLE:
for (size_t i = 0; i < entry->count; i++)
entry->value.dbl[i] = ff_tget_double(gb, le);
break;
case AV_TIFF_FLOAT:
for (size_t i = 0; i < entry->count; i++) {
av_alias32 alias = { .u32 = ff_tget_long(gb, le) };
entry->value.dbl[i] = alias.f32;
}
break;
case AV_TIFF_RATIONAL:
case AV_TIFF_SRATIONAL:
for (size_t i = 0; i < entry->count; i++) {
int32_t num = ff_tget_long(gb, le);
int32_t den = ff_tget_long(gb, le);
entry->value.rat[i] = av_make_q(num, den);
}
break;
case AV_TIFF_UNDEFINED:
case AV_TIFF_BYTE:
/* these three fields are aliased to entry->value.ptr via a union */
/* and entry->value.ptr will always be nonzero here */
av_assert0(entry->value.ubytes);
bytestream2_get_buffer(gb, entry->value.ubytes, entry->count);
break;
case AV_TIFF_SBYTE:
av_assert0(entry->value.sbytes);
bytestream2_get_buffer(gb, entry->value.sbytes, entry->count);
break;
case AV_TIFF_STRING:
av_assert0(entry->value.str);
bytestream2_get_buffer(gb, entry->value.str, entry->count);
break;
}
return 0;
}
static void exif_write_values(PutByteContext *pb, int le, const AVExifEntry *entry)
{
switch (entry->type) {
case AV_TIFF_SHORT:
for (size_t i = 0; i < entry->count; i++)
tput16(pb, le, entry->value.uint[i]);
break;
case AV_TIFF_LONG:
for (size_t i = 0; i < entry->count; i++)
tput32(pb, le, entry->value.uint[i]);
break;
case AV_TIFF_SSHORT:
for (size_t i = 0; i < entry->count; i++)
tput16(pb, le, entry->value.sint[i]);
break;
case AV_TIFF_SLONG:
for (size_t i = 0; i < entry->count; i++)
tput32(pb, le, entry->value.sint[i]);
break;
case AV_TIFF_DOUBLE:
for (size_t i = 0; i < entry->count; i++) {
const av_alias64 a = { .f64 = entry->value.dbl[i] };
tput64(pb, le, a.u64);
}
break;
case AV_TIFF_FLOAT:
for (size_t i = 0; i < entry->count; i++) {
const av_alias32 a = { .f32 = entry->value.dbl[i] };
tput32(pb, le, a.u32);
}
break;
case AV_TIFF_RATIONAL:
case AV_TIFF_SRATIONAL:
for (size_t i = 0; i < entry->count; i++) {
tput32(pb, le, entry->value.rat[i].num);
tput32(pb, le, entry->value.rat[i].den);
}
break;
case AV_TIFF_UNDEFINED:
case AV_TIFF_BYTE:
bytestream2_put_buffer(pb, entry->value.ubytes, entry->count);
break;
case AV_TIFF_SBYTE:
bytestream2_put_buffer(pb, entry->value.sbytes, entry->count);
break;
case AV_TIFF_STRING:
bytestream2_put_buffer(pb, entry->value.str, entry->count);
break;
}
}
static const uint8_t aoc_header[] = { 'A', 'O', 'C', 0, };
static const uint8_t casio_header[] = { 'Q', 'V', 'C', 0, 0, 0, };
static const uint8_t foveon_header[] = { 'F', 'O', 'V', 'E', 'O', 'N', 0, 0, };
static const uint8_t fuji_header[] = { 'F', 'U', 'J', 'I', };
static const uint8_t nikon_header[] = { 'N', 'i', 'k', 'o', 'n', 0, };
static const uint8_t olympus1_header[] = { 'O', 'L', 'Y', 'M', 'P', 0, };
static const uint8_t olympus2_header[] = { 'O', 'L', 'Y', 'M', 'P', 'U', 'S', 0, 'I', 'I', };
static const uint8_t panasonic_header[] = { 'P', 'a', 'n', 'a', 's', 'o', 'n', 'i', 'c', 0, 0, 0, };
static const uint8_t sigma_header[] = { 'S', 'I', 'G', 'M', 'A', 0, 0, 0, };
static const uint8_t sony_header[] = { 'S', 'O', 'N', 'Y', ' ', 'D', 'S', 'C', ' ', 0, 0, 0, };
struct exif_makernote_data {
const uint8_t *header;
size_t header_size;
int result;
};
#define MAKERNOTE_STRUCT(h, r) { \
.header = (h), \
.header_size = sizeof((h)), \
.result = (r), \
}
static const struct exif_makernote_data makernote_data[] = {
MAKERNOTE_STRUCT(aoc_header, 6),
MAKERNOTE_STRUCT(casio_header, -1),
MAKERNOTE_STRUCT(foveon_header, 10),
MAKERNOTE_STRUCT(fuji_header, -1),
MAKERNOTE_STRUCT(olympus1_header, 8),
MAKERNOTE_STRUCT(olympus2_header, -1),
MAKERNOTE_STRUCT(panasonic_header, 12),
MAKERNOTE_STRUCT(sigma_header, 10),
MAKERNOTE_STRUCT(sony_header, 12),
};
/*
* derived from Exiv2 MakerNote's article
* https://exiv2.org/makernote.html or archived at
* https://web.archive.org/web/20250311155857/https://exiv2.org/makernote.html
*/
static int exif_get_makernote_offset(GetByteContext *gb)
{
if (bytestream2_get_bytes_left(gb) < BASE_TAG_SIZE)
return -1;
for (int i = 0; i < FF_ARRAY_ELEMS(makernote_data); i++) {
if (!memcmp(gb->buffer, makernote_data[i].header, makernote_data[i].header_size))
return makernote_data[i].result;
}
if (!memcmp(gb->buffer, nikon_header, sizeof(nikon_header))) {
if (bytestream2_get_bytes_left(gb) < 14)
return -1;
else if (AV_RB32(gb->buffer + 10) == EXIF_MM_LONG || AV_RB32(gb->buffer + 10) == EXIF_II_LONG)
return -1;
return 8;
}
return 0;
}
static int exif_parse_ifd_list(void *logctx, GetByteContext *gb, int le,
int depth, AVExifMetadata *ifd, int guess);
static int exif_decode_tag(void *logctx, GetByteContext *gb, int le,
int depth, AVExifEntry *entry)
{
int ret = 0, makernote_offset = -1, tell, is_ifd, count;
enum AVTiffDataType type;
uint32_t payload;
/* safety check to prevent infinite recursion on malicious IFDs */
if (depth > 3)
return AVERROR_INVALIDDATA;
tell = bytestream2_tell(gb);
entry->id = ff_tget_short(gb, le);
type = ff_tget_short(gb, le);
count = ff_tget_long(gb, le);
payload = ff_tget_long(gb, le);
av_log(logctx, AV_LOG_DEBUG, "TIFF Tag: id: 0x%04x, type: %d, count: %u, offset: %d, "
"payload: %" PRIu32 "\n", entry->id, type, count, tell, payload);
/* AV_TIFF_IFD is the largest, numerically */
if (type > AV_TIFF_IFD || count >= INT_MAX/8U)
return AVERROR_INVALIDDATA;
is_ifd = type == AV_TIFF_IFD || ff_tis_ifd(entry->id) || entry->id == MAKERNOTE_TAG;
if (is_ifd) {
if (!payload)
goto end;
bytestream2_seek(gb, payload, SEEK_SET);
}
if (entry->id == MAKERNOTE_TAG) {
makernote_offset = exif_get_makernote_offset(gb);
if (makernote_offset < 0)
is_ifd = 0;
}
if (is_ifd) {
entry->type = AV_TIFF_IFD;
entry->count = 1;
entry->ifd_offset = makernote_offset > 0 ? makernote_offset : 0;
if (entry->ifd_offset) {
entry->ifd_lead = av_malloc(entry->ifd_offset);
if (!entry->ifd_lead)
return AVERROR(ENOMEM);
bytestream2_get_buffer(gb, entry->ifd_lead, entry->ifd_offset);
}
ret = exif_parse_ifd_list(logctx, gb, le, depth + 1, &entry->value.ifd, entry->id == MAKERNOTE_TAG);
if (ret < 0 && entry->id == MAKERNOTE_TAG) {
/*
* we guessed that MakerNote was an IFD
* but we were probably incorrect at this
* point so we try again as a binary blob
*/
av_log(logctx, AV_LOG_DEBUG, "unrecognized MakerNote IFD, retrying as blob\n");
is_ifd = 0;
}
}
/* inverted condition instead of else so we can fall through from above */
if (!is_ifd) {
entry->type = type == AV_TIFF_IFD ? AV_TIFF_UNDEFINED : type;
entry->count = count;
bytestream2_seek(gb, count * exif_sizes[type] > 4 ? payload : tell + 8, SEEK_SET);
ret = exif_read_values(logctx, gb, le, entry);
}
end:
bytestream2_seek(gb, tell + BASE_TAG_SIZE, SEEK_SET);
return ret;
}
static int exif_parse_ifd_list(void *logctx, GetByteContext *gb, int le,
int depth, AVExifMetadata *ifd, int guess)
{
uint32_t entries;
size_t required_size;
void *temp;
int ret = 0;
av_log(logctx, AV_LOG_DEBUG, "parsing IFD list at offset: %d\n", bytestream2_tell(gb));
if (bytestream2_get_bytes_left(gb) < 2) {
av_log(logctx, guess ? AV_LOG_DEBUG : AV_LOG_ERROR,
"not enough bytes remaining in EXIF buffer: 2 required\n");
ret = AVERROR_INVALIDDATA;
goto end;
}
entries = ff_tget_short(gb, le);
if (bytestream2_get_bytes_left(gb) < entries * BASE_TAG_SIZE) {
av_log(logctx, guess ? AV_LOG_DEBUG : AV_LOG_ERROR,
"not enough bytes remaining in EXIF buffer. entries: %" PRIu32 "\n", entries);
ret = AVERROR_INVALIDDATA;
goto end;
}
if (entries > 4096) {
/* that is a lot of entries, probably an error */
av_log(logctx, guess ? AV_LOG_DEBUG : AV_LOG_ERROR,
"too many entries: %" PRIu32 "\n", entries);
ret = AVERROR_INVALIDDATA;
goto end;
}
ifd->count = entries;
av_log(logctx, AV_LOG_DEBUG, "entry count for IFD: %u\n", ifd->count);
/* empty IFD is technically legal but equivalent to no metadata present */
if (!ifd->count) {
ret = 0;
goto end;
}
if (av_size_mult(ifd->count, sizeof(*ifd->entries), &required_size) < 0) {
ret = AVERROR(ENOMEM);
goto end;
}
temp = av_fast_realloc(ifd->entries, &ifd->size, required_size);
if (!temp) {
av_freep(&ifd->entries);
ret = AVERROR(ENOMEM);
goto end;
}
ifd->entries = temp;
/* entries have pointers in them which can cause issues if */
/* they are freed or realloc'd when garbage */
memset(ifd->entries, 0, required_size);
for (uint32_t i = 0; i < entries; i++) {
ret = exif_decode_tag(logctx, gb, le, depth, &ifd->entries[i]);
if (ret < 0)
goto end;
}
end:
if (ret < 0) {
av_exif_free(ifd);
return ret;
}
/*
* at the end of an IFD is an pointer to the next IFD
* or zero if there are no more IFDs, which is usually the case
*/
ret = ff_tget_long(gb, le);
/* overflow */
if (ret < 0) {
ret = AVERROR_INVALIDDATA;
av_exif_free(ifd);
}
return ret;
}
/*
* note that this function does not free the entry pointer itself
* because it's probably part of a larger array that should be freed
* all at once
*/
static void exif_free_entry(AVExifEntry *entry)
{
if (!entry)
return;
if (entry->type == AV_TIFF_IFD)
av_exif_free(&entry->value.ifd);
else
av_freep(&entry->value.ptr);
av_freep(&entry->ifd_lead);
}
void av_exif_free(AVExifMetadata *ifd)
{
if (!ifd)
return;
if (!ifd->entries) {
ifd->count = 0;
ifd->size = 0;
return;
}
for (size_t i = 0; i < ifd->count; i++) {
AVExifEntry *entry = &ifd->entries[i];
exif_free_entry(entry);
}
av_freep(&ifd->entries);
ifd->count = 0;
ifd->size = 0;
}
static size_t exif_get_ifd_size(const AVExifMetadata *ifd)
{
/* 6 == 4 + 2; 2-byte entry-count at the beginning */
/* plus 4-byte next-IFD pointer at the end */
size_t total_size = IFD_EXTRA_SIZE;
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;
} else {
size_t payload_size = entry->count * exif_sizes[entry->type];
total_size += BASE_TAG_SIZE + (payload_size > 4 ? payload_size : 0);
}
}
return total_size;
}
static int exif_write_ifd(void *logctx, PutByteContext *pb, int le, int depth, const AVExifMetadata *ifd)
{
int offset, ret, tell, tell2;
tell = bytestream2_tell_p(pb);
tput16(pb, le, ifd->count);
offset = tell + IFD_EXTRA_SIZE + BASE_TAG_SIZE * (uint32_t) ifd->count;
av_log(logctx, AV_LOG_DEBUG, "writing IFD with %u entries and initial offset %d\n", ifd->count, offset);
for (size_t i = 0; i < ifd->count; i++) {
const AVExifEntry *entry = &ifd->entries[i];
av_log(logctx, AV_LOG_DEBUG, "writing TIFF entry: id: 0x%04" PRIx16 ", type: %d, count: %"
PRIu32 ", offset: %d, offset value: %d\n",
entry->id, entry->type, entry->count,
bytestream2_tell_p(pb), offset);
tput16(pb, le, entry->id);
if (entry->id == MAKERNOTE_TAG && entry->type == AV_TIFF_IFD) {
size_t ifd_size = exif_get_ifd_size(&entry->value.ifd);
tput16(pb, le, AV_TIFF_UNDEFINED);
tput32(pb, le, ifd_size);
} else {
tput16(pb, le, entry->type);
tput32(pb, le, entry->count);
}
if (entry->type == AV_TIFF_IFD) {
tput32(pb, le, offset);
tell2 = bytestream2_tell_p(pb);
bytestream2_seek_p(pb, offset, SEEK_SET);
if (entry->ifd_offset)
bytestream2_put_buffer(pb, entry->ifd_lead, entry->ifd_offset);
ret = exif_write_ifd(logctx, pb, le, depth + 1, &entry->value.ifd);
if (ret < 0)
return ret;
offset += ret + entry->ifd_offset;
bytestream2_seek_p(pb, tell2, SEEK_SET);
} else {
size_t payload_size = entry->count * exif_sizes[entry->type];
if (payload_size > 4) {
tput32(pb, le, offset);
tell2 = bytestream2_tell_p(pb);
bytestream2_seek_p(pb, offset, SEEK_SET);
exif_write_values(pb, le, entry);
offset += payload_size;
bytestream2_seek_p(pb, tell2, SEEK_SET);
} else {
/* zero uninitialized excess payload values */
AV_WN32(pb->buffer, 0);
exif_write_values(pb, le, entry);
bytestream2_seek_p(pb, 4 - payload_size, SEEK_CUR);
}
}
}
/*
* we write 0 if this is the top-level exif IFD
* indicating that there are no more IFD pointers
*/
tput32(pb, le, depth ? offset : 0);
return offset - tell;
}
int av_exif_write(void *logctx, const AVExifMetadata *ifd, AVBufferRef **buffer, enum AVExifHeaderMode header_mode)
{
AVBufferRef *buf = NULL;
size_t size, headsize = 8;
PutByteContext pb;
int ret = 0, off = 0, next;
AVExifMetadata *ifd_new = NULL;
AVExifMetadata extra_ifds[16] = { 0 };
int le = 1;
if (*buffer) {
ret = AVERROR(EINVAL);
goto end;
}
size = exif_get_ifd_size(ifd);
switch (header_mode) {
case AV_EXIF_EXIF00:
off = 6;
break;
case AV_EXIF_T_OFF:
off = 4;
break;
case AV_EXIF_ASSUME_BE:
le = 0;
headsize = 0;
break;
case AV_EXIF_ASSUME_LE:
le = 1;
headsize = 0;
break;
}
ret = av_buffer_realloc(&buf, size + off + headsize);
if (ret < 0)
goto end;
if (header_mode == AV_EXIF_EXIF00) {
AV_WL32(buf->data, MKTAG('E','x','i','f'));
AV_WN16(buf->data + 4, 0);
} else if (header_mode == AV_EXIF_T_OFF) {
AV_WN32(buf->data, 0);
}
bytestream2_init_writer(&pb, buf->data + off, buf->size - off);
if (header_mode != AV_EXIF_ASSUME_BE && header_mode != AV_EXIF_ASSUME_LE) {
/* these constants are be32 in both cases */
/* le == 1 always in this case */
bytestream2_put_be32(&pb, EXIF_II_LONG);
tput32(&pb, le, 8);
}
int extras = 0;
for (int i = 0; i < FF_ARRAY_ELEMS(extra_ifds); i++) {
AVExifEntry *extra_entry = NULL;
uint16_t extra_tag = 0xFFFCu - i;
ret = av_exif_get_entry(logctx, (AVExifMetadata *) ifd, extra_tag, 0, &extra_entry);
if (ret < 0)
break;
if (!ret)
continue;
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;
}
if (ret < 0) {
av_log(logctx, AV_LOG_ERROR, "error popping additional IFD: %s\n", av_err2str(ret));
goto end;
}
next = bytestream2_tell_p(&pb);
ret = exif_write_ifd(logctx, &pb, le, 0, ifd);
if (ret < 0) {
av_log(logctx, AV_LOG_ERROR, "error writing EXIF data: %s\n", av_err2str(ret));
goto end;
}
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) {
av_log(logctx, AV_LOG_ERROR, "error writing additional IFD: %s\n", av_err2str(ret));
goto end;
}
next += ret;
}
/* shrink the buffer to the amount of data we actually used */
/* extras don't contribute the initial BASE_TAG_SIZE each */
ret = av_buffer_realloc(&buf, buf->size - BASE_TAG_SIZE * extras);
if (ret < 0)
goto end;
*buffer = buf;
ret = 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]);
if (ret < 0)
av_buffer_unref(&buf);
return ret;
}
int av_exif_parse_buffer(void *logctx, const uint8_t *buf, size_t size,
AVExifMetadata *ifd, enum AVExifHeaderMode header_mode)
{
int ret, le;
GetByteContext gbytes;
if (size > INT_MAX)
return AVERROR(EINVAL);
size_t off = 0;
switch (header_mode) {
case AV_EXIF_EXIF00:
if (size < 6)
return AVERROR_INVALIDDATA;
off = 6;
/* fallthrough */
case AV_EXIF_T_OFF:
if (size < 4)
return AVERROR_INVALIDDATA;
if (!off)
off = AV_RB32(buf) + 4;
/* fallthrough */
case AV_EXIF_TIFF_HEADER: {
int ifd_offset;
if (size <= off)
return AVERROR_INVALIDDATA;
bytestream2_init(&gbytes, buf + off, size - off);
// read TIFF header
ret = ff_tdecode_header(&gbytes, &le, &ifd_offset);
if (ret < 0) {
av_log(logctx, AV_LOG_ERROR, "invalid TIFF header in EXIF data: %s\n", av_err2str(ret));
return ret;
}
bytestream2_seek(&gbytes, ifd_offset, SEEK_SET);
break;
}
case AV_EXIF_ASSUME_LE:
le = 1;
bytestream2_init(&gbytes, buf, size);
break;
case AV_EXIF_ASSUME_BE:
le = 0;
bytestream2_init(&gbytes, buf, size);
break;
default:
return AVERROR(EINVAL);
}
/*
* parse IFD0 here. If the return value is positive that tells us
* there is subimage metadata, but we don't parse that IFD here
*/
ret = exif_parse_ifd_list(logctx, &gbytes, le, 0, ifd, 0);
if (ret < 0) {
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);
/* 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") : "")
static int exif_ifd_to_dict(void *logctx, const char *prefix, const AVExifMetadata *ifd, AVDictionary **metadata)
{
AVBPrint bp;
int ret = 0;
char *key = NULL;
char *value = NULL;
if (!prefix)
prefix = "";
for (uint16_t i = 0; i < ifd->count; i++) {
const AVExifEntry *entry = &ifd->entries[i];
const char *name = av_exif_get_tag_name(entry->id);
av_bprint_init(&bp, entry->count * 10, AV_BPRINT_SIZE_UNLIMITED);
if (*prefix)
av_bprintf(&bp, "%s/", prefix);
if (name)
av_bprintf(&bp, "%s", name);
else
av_bprintf(&bp, "0x%04X", entry->id);
ret = av_bprint_finalize(&bp, &key);
if (ret < 0)
goto end;
av_bprint_init(&bp, entry->count * 10, AV_BPRINT_SIZE_UNLIMITED);
switch (entry->type) {
case AV_TIFF_IFD:
ret = exif_ifd_to_dict(logctx, key, &entry->value.ifd, metadata);
if (ret < 0)
goto end;
break;
case AV_TIFF_SHORT:
case AV_TIFF_LONG:
for (uint32_t j = 0; j < entry->count; j++)
av_bprintf(&bp, "%s%7" PRIu32, COLUMN_SEP(j, 8), (uint32_t)entry->value.uint[j]);
break;
case AV_TIFF_SSHORT:
case AV_TIFF_SLONG:
for (uint32_t j = 0; j < entry->count; j++)
av_bprintf(&bp, "%s%7" PRId32, COLUMN_SEP(j, 8), (int32_t)entry->value.sint[j]);
break;
case AV_TIFF_RATIONAL:
case AV_TIFF_SRATIONAL:
for (uint32_t j = 0; j < entry->count; j++)
av_bprintf(&bp, "%s%7i:%-7i", COLUMN_SEP(j, 4), entry->value.rat[j].num, entry->value.rat[j].den);
break;
case AV_TIFF_DOUBLE:
case AV_TIFF_FLOAT:
for (uint32_t j = 0; j < entry->count; j++)
av_bprintf(&bp, "%s%.15g", COLUMN_SEP(j, 4), entry->value.dbl[j]);
break;
case AV_TIFF_STRING:
av_bprintf(&bp, "%s", entry->value.str);
break;
case AV_TIFF_UNDEFINED:
case AV_TIFF_BYTE:
for (uint32_t j = 0; j < entry->count; j++)
av_bprintf(&bp, "%s%3i", COLUMN_SEP(j, 16), entry->value.ubytes[j]);
break;
case AV_TIFF_SBYTE:
for (uint32_t j = 0; j < entry->count; j++)
av_bprintf(&bp, "%s%3i", COLUMN_SEP(j, 16), entry->value.sbytes[j]);
break;
}
if (entry->type != AV_TIFF_IFD) {
if (!av_bprint_is_complete(&bp)) {
av_bprint_finalize(&bp, NULL);
ret = AVERROR(ENOMEM);
goto end;
}
ret = av_bprint_finalize(&bp, &value);
if (ret < 0)
goto end;
ret = av_dict_set(metadata, key, value, AV_DICT_DONT_STRDUP_KEY | AV_DICT_DONT_STRDUP_VAL);
key = NULL;
value = NULL;
if (ret < 0)
goto end;
} else {
av_freep(&key);
}
}
end:
av_freep(&key);
av_freep(&value);
return ret;
}
int av_exif_ifd_to_dict(void *logctx, const AVExifMetadata *ifd, AVDictionary **metadata)
{
return exif_ifd_to_dict(logctx, "", ifd, metadata);
}
#if LIBAVCODEC_VERSION_MAJOR < 63
int avpriv_exif_decode_ifd(void *logctx, const uint8_t *buf, int size,
int le, int depth, AVDictionary **metadata)
{
AVExifMetadata ifd = { 0 };
GetByteContext gb;
int ret;
bytestream2_init(&gb, buf, size);
ret = exif_parse_ifd_list(logctx, &gb, le, depth, &ifd, 0);
if (ret < 0)
return ret;
ret = av_exif_ifd_to_dict(logctx, &ifd, metadata);
av_exif_free(&ifd);
return ret;
}
#endif
#define EXIF_COPY(fname, srcname) do { \
size_t sz; \
if (av_size_mult(src->count, sizeof(*(fname)), &sz) < 0) { \
ret = AVERROR(ENOMEM); \
goto end; \
} \
(fname) = av_memdup((srcname), sz); \
if (!(fname)) { \
ret = AVERROR(ENOMEM); \
goto end; \
} \
} while (0)
static int exif_clone_entry(AVExifEntry *dst, const AVExifEntry *src)
{
int ret = 0;
memset(dst, 0, sizeof(*dst));
dst->count = src->count;
dst->id = src->id;
dst->type = src->type;
dst->ifd_offset = src->ifd_offset;
if (src->ifd_lead) {
dst->ifd_lead = av_memdup(src->ifd_lead, src->ifd_offset);
if (!dst->ifd_lead) {
ret = AVERROR(ENOMEM);
goto end;
}
} else {
dst->ifd_lead = NULL;
}
switch(src->type) {
case AV_TIFF_IFD: {
AVExifMetadata *cloned = av_exif_clone_ifd(&src->value.ifd);
if (!cloned) {
ret = AVERROR(ENOMEM);
goto end;
}
dst->value.ifd = *cloned;
av_freep(&cloned);
break;
}
case AV_TIFF_SHORT:
case AV_TIFF_LONG:
EXIF_COPY(dst->value.uint, src->value.uint);
break;
case AV_TIFF_SLONG:
case AV_TIFF_SSHORT:
EXIF_COPY(dst->value.sint, src->value.sint);
break;
case AV_TIFF_RATIONAL:
case AV_TIFF_SRATIONAL:
EXIF_COPY(dst->value.rat, src->value.rat);
break;
case AV_TIFF_DOUBLE:
case AV_TIFF_FLOAT:
EXIF_COPY(dst->value.dbl, src->value.dbl);
break;
case AV_TIFF_BYTE:
case AV_TIFF_UNDEFINED:
EXIF_COPY(dst->value.ubytes, src->value.ubytes);
break;
case AV_TIFF_SBYTE:
EXIF_COPY(dst->value.sbytes, src->value.sbytes);
break;
case AV_TIFF_STRING:
dst->value.str = av_memdup(src->value.str, src->count+1);
if (!dst->value.str) {
ret = AVERROR(ENOMEM);
goto end;
}
break;
}
return 0;
end:
av_freep(&dst->ifd_lead);
if (src->type == AV_TIFF_IFD)
av_exif_free(&dst->value.ifd);
else
av_freep(&dst->value.ptr);
memset(dst, 0, sizeof(*dst));
return ret;
}
static int exif_get_entry(void *logctx, AVExifMetadata *ifd, uint16_t id, int depth, AVExifEntry **value)
{
int offset = 1;
if (!ifd || ifd->count && !ifd->entries || !value)
return AVERROR(EINVAL);
for (size_t i = 0; i < ifd->count; i++) {
if (ifd->entries[i].id == id) {
*value = &ifd->entries[i];
return i + offset;
}
if (ifd->entries[i].type == AV_TIFF_IFD) {
if (depth < 3) {
int ret = exif_get_entry(logctx, &ifd->entries[i].value.ifd, id, depth + 1, value);
if (ret)
return ret < 0 ? ret : ret + offset;
}
offset += ifd->entries[i].value.ifd.count;
}
}
return 0;
}
int av_exif_get_entry(void *logctx, AVExifMetadata *ifd, uint16_t id, int flags, AVExifEntry **value)
{
return exif_get_entry(logctx, ifd, id, (flags & AV_EXIF_FLAG_RECURSIVE) ? 0 : INT_MAX, value);
}
int av_exif_set_entry(void *logctx, AVExifMetadata *ifd, uint16_t id, enum AVTiffDataType type,
uint32_t count, const uint8_t *ifd_lead, uint32_t ifd_offset, const void *value)
{
void *temp;
int ret = 0;
AVExifEntry *entry = NULL;
AVExifEntry src = { 0 };
if (!ifd || ifd->count && !ifd->entries
|| ifd_lead && !ifd_offset || !ifd_lead && ifd_offset
|| !value || ifd->count == 0xFFFFu)
return AVERROR(EINVAL);
ret = av_exif_get_entry(logctx, ifd, id, 0, &entry);
if (ret < 0)
return ret;
if (entry) {
exif_free_entry(entry);
} else {
size_t required_size;
ret = av_size_mult(ifd->count + 1, sizeof(*ifd->entries), &required_size);
if (ret < 0)
return AVERROR(ENOMEM);
temp = av_fast_realloc(ifd->entries, &ifd->size, required_size);
if (!temp)
return AVERROR(ENOMEM);
ifd->entries = temp;
entry = &ifd->entries[ifd->count++];
}
src.count = count;
src.id = id;
src.type = type;
src.ifd_lead = (uint8_t *) ifd_lead;
src.ifd_offset = ifd_offset;
if (type == AV_TIFF_IFD)
src.value.ifd = * (const AVExifMetadata *) value;
else
src.value.ptr = (void *) value;
ret = exif_clone_entry(entry, &src);
if (ret < 0)
ifd->count--;
return ret;
}
static int exif_remove_entry(void *logctx, AVExifMetadata *ifd, uint16_t id, int depth)
{
int32_t index = -1;
int ret = 0;
if (!ifd || ifd->count && !ifd->entries)
return AVERROR(EINVAL);
for (size_t i = 0; i < ifd->count; i++) {
if (ifd->entries[i].id == id) {
index = i;
break;
}
if (ifd->entries[i].type == AV_TIFF_IFD && depth < 3) {
ret = exif_remove_entry(logctx, &ifd->entries[i].value.ifd, id, depth + 1);
if (ret)
return ret;
}
}
if (index < 0)
return 0;
exif_free_entry(&ifd->entries[index]);
if (index == --ifd->count) {
if (!index)
av_freep(&ifd->entries);
return 1;
}
memmove(&ifd->entries[index], &ifd->entries[index + 1], (ifd->count - index) * sizeof(*ifd->entries));
return 1 + (ifd->count - index);
}
int av_exif_remove_entry(void *logctx, AVExifMetadata *ifd, uint16_t id, int flags)
{
return exif_remove_entry(logctx, ifd, id, (flags & AV_EXIF_FLAG_RECURSIVE) ? 0 : INT_MAX);
}
AVExifMetadata *av_exif_clone_ifd(const AVExifMetadata *ifd)
{
AVExifMetadata *ret = av_mallocz(sizeof(*ret));
if (!ret)
return NULL;
ret->count = ifd->count;
if (ret->count) {
size_t required_size;
if (av_size_mult(ret->count, sizeof(*ret->entries), &required_size) < 0)
goto fail;
av_fast_mallocz(&ret->entries, &ret->size, required_size);
if (!ret->entries)
goto fail;
}
for (size_t i = 0; i < ret->count; i++) {
const AVExifEntry *entry = &ifd->entries[i];
AVExifEntry *ret_entry = &ret->entries[i];
int status = exif_clone_entry(ret_entry, entry);
if (status < 0)
goto fail;
}
return ret;
fail:
av_exif_free(ret);
av_free(ret);
return NULL;
}
static const int rotation_lut[2][4] = {
{1, 8, 3, 6}, {4, 7, 2, 5},
};
int av_exif_matrix_to_orientation(const int32_t *matrix)
{
double rotation = av_display_rotation_get(matrix);
// determinant
int vflip = ((int64_t)matrix[0] * (int64_t)matrix[4]
- (int64_t)matrix[1] * (int64_t)matrix[3]) < 0;
if (!isfinite(rotation))
return 0;
int rot = (int)(rotation + 0.5);
rot = (((rot % 360) + 360) % 360) / 90;
return rotation_lut[vflip][rot];
}
int av_exif_orientation_to_matrix(int32_t *matrix, int orientation)
{
switch (orientation) {
case 1:
av_display_rotation_set(matrix, 0.0);
break;
case 2:
av_display_rotation_set(matrix, 0.0);
av_display_matrix_flip(matrix, 1, 0);
break;
case 3:
av_display_rotation_set(matrix, 180.0);
break;
case 4:
av_display_rotation_set(matrix, 180.0);
av_display_matrix_flip(matrix, 1, 0);
break;
case 5:
av_display_rotation_set(matrix, 90.0);
av_display_matrix_flip(matrix, 1, 0);
break;
case 6:
av_display_rotation_set(matrix, 90.0);
break;
case 7:
av_display_rotation_set(matrix, -90.0);
av_display_matrix_flip(matrix, 1, 0);
break;
case 8:
av_display_rotation_set(matrix, -90.0);
break;
default:
return AVERROR(EINVAL);
}
return 0;
}
int ff_exif_sanitize_ifd(void *logctx, const AVFrame *frame, AVExifMetadata *ifd)
{
int ret = 0;
AVFrameSideData *sd_orient = NULL;
AVExifEntry *or = NULL;
AVExifEntry *iw = NULL;
AVExifEntry *ih = NULL;
AVExifEntry *pw = NULL;
AVExifEntry *ph = NULL;
uint64_t orientation = 1;
uint64_t w = frame->width;
uint64_t h = frame->height;
int rewrite = 0;
sd_orient = av_frame_get_side_data(frame, AV_FRAME_DATA_DISPLAYMATRIX);
if (sd_orient)
orientation = av_exif_matrix_to_orientation((int32_t *) sd_orient->data);
if (orientation != 1)
av_log(logctx, AV_LOG_DEBUG, "matrix contains nontrivial EXIF orientation: %" PRIu64 "\n", orientation);
for (size_t i = 0; i < ifd->count; i++) {
AVExifEntry *entry = &ifd->entries[i];
if (entry->id == ORIENTATION_TAG && entry->count > 0 && entry->type == AV_TIFF_SHORT) {
or = entry;
continue;
}
if (entry->id == IMAGE_WIDTH_TAG && entry->count > 0 && entry->type == AV_TIFF_LONG) {
iw = entry;
continue;
}
if (entry->id == IMAGE_LENGTH_TAG && entry->count > 0 && entry->type == AV_TIFF_LONG) {
ih = entry;
continue;
}
if (entry->id == EXIFIFD_TAG && entry->type == AV_TIFF_IFD) {
AVExifMetadata *exif = &entry->value.ifd;
for (size_t j = 0; j < exif->count; j++) {
AVExifEntry *exifentry = &exif->entries[j];
if (exifentry->id == PIXEL_X_TAG && exifentry->count > 0 && exifentry->type == AV_TIFF_SHORT) {
pw = exifentry;
continue;
}
if (exifentry->id == PIXEL_Y_TAG && exifentry->count > 0 && exifentry->type == AV_TIFF_SHORT) {
ph = exifentry;
continue;
}
}
}
}
if (or && or->value.uint[0] != orientation) {
rewrite = 1;
or->value.uint[0] = orientation;
}
if (iw && iw->value.uint[0] != w) {
rewrite = 1;
iw->value.uint[0] = w;
}
if (ih && ih->value.uint[0] != h) {
rewrite = 1;
ih->value.uint[0] = h;
}
if (pw && pw->value.uint[0] != w) {
rewrite = 1;
pw->value.uint[0] = w;
}
if (ph && ph->value.uint[0] != h) {
rewrite = 1;
ph->value.uint[0] = h;
}
if (!or && orientation != 1) {
rewrite = 1;
ret = av_exif_set_entry(logctx, ifd, ORIENTATION_TAG, AV_TIFF_SHORT, 1, NULL, 0, &orientation);
if (ret < 0)
goto end;
}
if (!iw && w) {
rewrite = 1;
ret = av_exif_set_entry(logctx, ifd, IMAGE_WIDTH_TAG, AV_TIFF_LONG, 1, NULL, 0, &w);
if (ret < 0)
goto end;
}
if (!ih && h) {
rewrite = 1;
ret = av_exif_set_entry(logctx, ifd, IMAGE_LENGTH_TAG, AV_TIFF_LONG, 1, NULL, 0, &h);
if (ret < 0)
goto end;
}
if (!pw && w && w < 0xFFFFu || !ph && h && h < 0xFFFFu) {
AVExifMetadata *exif;
AVExifEntry *exif_entry;
int exif_found = av_exif_get_entry(logctx, ifd, EXIFIFD_TAG, 0, &exif_entry);
rewrite = 1;
if (exif_found < 0)
goto end;
if (exif_found > 0) {
exif = &exif_entry->value.ifd;
} else {
AVExifMetadata exif_new = { 0 };
ret = av_exif_set_entry(logctx, ifd, EXIFIFD_TAG, AV_TIFF_IFD, 1, NULL, 0, &exif_new);
if (ret < 0) {
av_exif_free(&exif_new);
goto end;
}
exif = &ifd->entries[ifd->count - 1].value.ifd;
}
if (!pw && w && w < 0xFFFFu) {
ret = av_exif_set_entry(logctx, exif, PIXEL_X_TAG, AV_TIFF_SHORT, 1, NULL, 0, &w);
if (ret < 0)
goto end;
}
if (!ph && h && h < 0xFFFFu) {
ret = av_exif_set_entry(logctx, exif, PIXEL_Y_TAG, AV_TIFF_SHORT, 1, NULL, 0, &h);
if (ret < 0)
goto end;
}
}
return rewrite;
end:
return ret;
}
int ff_exif_get_buffer(void *logctx, const AVFrame *frame, AVBufferRef **buffer_ptr, enum AVExifHeaderMode header_mode)
{
AVFrameSideData *sd_exif = NULL;
AVBufferRef *buffer = NULL;
AVExifMetadata ifd = { 0 };
int ret = 0;
int rewrite = 0;
if (!buffer_ptr || *buffer_ptr)
return AVERROR(EINVAL);
sd_exif = av_frame_get_side_data(frame, AV_FRAME_DATA_EXIF);
if (!sd_exif)
return 0;
ret = av_exif_parse_buffer(logctx, sd_exif->data, sd_exif->size, &ifd, AV_EXIF_TIFF_HEADER);
if (ret < 0)
goto end;
rewrite = ff_exif_sanitize_ifd(logctx, frame, &ifd);
if (rewrite < 0) {
ret = rewrite;
goto end;
}
if (rewrite) {
ret = av_exif_write(logctx, &ifd, &buffer, header_mode);
if (ret < 0)
goto end;
*buffer_ptr = buffer;
} else {
*buffer_ptr = av_buffer_ref(sd_exif->buf);
if (!*buffer_ptr) {
ret = AVERROR(ENOMEM);
goto end;
}
}
av_exif_free(&ifd);
return rewrite;
end:
av_exif_free(&ifd);
return ret;
}