From patchwork Mon Sep 16 14:43:42 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Leo Izen X-Patchwork-Id: 51621 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a59:9fc3:0:b0:48e:c0f8:d0de with SMTP id k3csp1399840vqy; Mon, 16 Sep 2024 08:09:16 -0700 (PDT) X-Forwarded-Encrypted: i=2; AJvYcCWj/8h5uuG/p0oZavXgCD/+86yRoKod23wnB8oZE8lcZ2T39zUK985dSilXHoLgmbDhgQFFXmKI4al74E/TJim6@gmail.com X-Google-Smtp-Source: AGHT+IFK/6p92AN9tL2evLHoWM1e4sQ73pl7FTw0BMWAngpIzcuhAHqJrHUwgRbwKvflJ7leDONB X-Received: by 2002:a05:6402:274f:b0:5bf:2577:4346 with SMTP id 4fb4d7f45d1cf-5c413e1b2a3mr15385977a12.15.1726499356308; Mon, 16 Sep 2024 08:09:16 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1726499356; cv=none; d=google.com; s=arc-20240605; b=MZH7C7VPaL0BIJPn7LeYqs0Ngz36taYUTHrx2UXjoPW3OcJCT5QW3Vgf+B4I/KkZs6 mN1mU37PBuiG7awZGyCpp96FaGK3vDhLZwWBPPFIW2I+Z3Gxuc7dH//sCE4DAItJNTEC Q15QsFq2vKQ6disy4/Wzufg8PsDYOQw+zlaRq1D6bNTywB0vmSoy0pvQCwHFAhRjBxGT vhvH5hvJj8uchgjKPsV2fZUQ/37FkLKLp3wlh1QBw2OLXXxvy2C0/48MRPjoD/xqvi06 zauHs9kDCPbbg8Jjuy3IPhgu+MWKw14GgCm/vx1z3S6LBSyoUioVn0kJixqfs4zfcU+R 3+5g== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=sender:errors-to:content-transfer-encoding:cc:reply-to :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:mime-version:references:in-reply-to :message-id:date:to:from:dkim-signature:delivered-to; bh=BvF8j80Rg8FoUEqUntrdIaWrZGaGQAAVWv2r2PMApJA=; fh=+bdjGe20eEUjtjncwA1dnEEVYNfJL4vyhV+sIRR4l+g=; b=lsfTPAu3Z040ywUe0h2ok96ckrYskpuB9hUlB8bkwd3N1C70uJxIzpL0PjWdQHIWzH KlkQeDmvz8SY7P40Ga3LG/MAm1nLHUcIuJpC8RDLX4JBU4Pu8El43410BZbnE+hOwmv/ Qgx8rUyoiYi7JoXqGWlQ3w52IDpjXHDa/tHkpH9Luqhcr4sc3EPf/+LO4iXfOtYxmLoK +h7QvlDL2yBiz//A1DnEguW+Y3elipROdNQZfttxRav9XGRPtU+9bABjqLNPW04EsNoj HZtqshqnVB7UwI8l97yHYnQP/yhFkrwlDGPGYUV9ONA3kwyrjPhr3chaQ/P6YnUv6M70 xC9Q==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20230601 header.b=EqhQWsMW; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com; dara=fail header.i=@gmail.com Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id 4fb4d7f45d1cf-5c42bc91a35si3923145a12.459.2024.09.16.08.09.15; Mon, 16 Sep 2024 08:09:16 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20230601 header.b=EqhQWsMW; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com; dara=fail header.i=@gmail.com Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 7E62668D6C1; Mon, 16 Sep 2024 17:44:15 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-qt1-f169.google.com (mail-qt1-f169.google.com [209.85.160.169]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id CFCB8689E58 for ; Mon, 16 Sep 2024 17:44:08 +0300 (EEST) Received: by mail-qt1-f169.google.com with SMTP id d75a77b69052e-45832b2784bso42184211cf.3 for ; Mon, 16 Sep 2024 07:44:08 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1726497847; x=1727102647; darn=ffmpeg.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=yactbW3O6iue7lIjszAbypIAnG1XZPzLMFOYQPPdtyI=; b=EqhQWsMWnGc4v0OJoFzLIrztTbmcM4c0wjMmchpLMmLPsMMJGpsRMyEJ/MgxPUie7r B6OUpCK8M1Z/UmPB8qAof3X5JmZdBb0HCMkrtk2XYBDS9CO2buh8/CBXCwlmcl0rOuJQ rTkNGN6UBXeKNBuFjYEKFO5zIgR954QX1htMWT0VI5ntnpG99b6rC6E2WiB7KSFmYRl8 cH1voPf1y8pxKLTIRRDOLpA55z1BGXYHRJ1NjxO+7cs9WTjzG++13DGPdf3PI77Rg2Km fe0XlzmTZN40bAIvVudXKsWXhyezUNkqM9PWTLlfIVGOORxJwHUn4tBArVKIvyBtHeUa KfQg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1726497847; x=1727102647; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=yactbW3O6iue7lIjszAbypIAnG1XZPzLMFOYQPPdtyI=; b=c5gSW54ZPqYBljRnS1jJMIUdF65fjYHr43QucinNzQI6H7kRgGyUOp1rqP8hH771ix 8QK0wZwAL15Tbt0skIlCjZCQxLWEq0neZPvcE7VeKKMqhQHpchabdvn+13znYj9KT0ce nZGtoXWVAz+e1lF1No79hQXkBkUW0tO+oc5fM9KsYCDU1s+8leJOwnJnhFDTo2Y9jmFt eJ3wdXSlZZEy5U5mkey6eQoovfp0LArwpbZugVu4IBIRx9HF0LlsCBFY//c8LVV1gjDl /b+luCIwHgtUqSiOumSObQWCpYjxQqV/3XO456uuBOjMWJwVFgpZ5bW9e5Tfwq/XOr6d 3gIQ== X-Gm-Message-State: AOJu0Yxyhq4cKBioFLQcOwmuW628euQH5Y1p+zzP1VPatkGzr4KgBvlk OswVz4cAINVIKVXLQPBE85zgsM1HE4x/vIoB+VEG2IQurDS01cITG7KCBw== X-Received: by 2002:a05:622a:4e07:b0:458:333b:3354 with SMTP id d75a77b69052e-458603e1081mr208826661cf.41.1726497846819; Mon, 16 Sep 2024 07:44:06 -0700 (PDT) Received: from gauss.local (c-68-56-149-176.hsd1.mi.comcast.net. [68.56.149.176]) by smtp.gmail.com with ESMTPSA id d75a77b69052e-459aac864c9sm27815501cf.38.2024.09.16.07.44.06 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 16 Sep 2024 07:44:06 -0700 (PDT) From: Leo Izen To: ffmpeg-devel@ffmpeg.org Date: Mon, 16 Sep 2024 10:43:42 -0400 Message-ID: <20240916144344.390716-2-leo.izen@gmail.com> X-Mailer: git-send-email 2.46.0 In-Reply-To: <20240916144344.390716-1-leo.izen@gmail.com> References: <20240916144344.390716-1-leo.izen@gmail.com> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH v2 1/3] various: change EXIF metadata into AVFrameSideData X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: Leo Izen Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: SA3cbPl2Fouc This patch centralizes much of the EXIF parsing and handling code for libavcodec, and delegates its own AVFrameSideData type to containing the buffer that holds EXIF metadata. This patch also adds exposes the exif parsing routing so it can be called by ffprobe, and updates the corresponding FATE tests to read the keys from the side data instead of from the main frame metadata. This commit also deprecates an avpriv_ function in exif.h, exposing the parsing functionality as a public API in the exported libavcodec/exif.h header file. Signed-off-by: Leo Izen --- fftools/ffprobe.c | 27 +- libavcodec/Makefile | 1 + libavcodec/exif.c | 394 +++++++++++++++++++++++++++-- libavcodec/exif.h | 32 ++- libavcodec/exif_internal.h | 55 ++++ libavcodec/mjpegdec.c | 91 +------ libavcodec/mjpegdec.h | 2 +- libavcodec/tiff.c | 19 +- libavcodec/tiff.h | 1 + libavcodec/version.h | 2 +- libavcodec/webp.c | 38 +-- libavformat/avidec.c | 4 +- libavutil/frame.c | 2 + libavutil/frame.h | 6 + tests/ref/fate/exif-image-embedded | 5 +- tests/ref/fate/exif-image-jpg | 91 +++---- tests/ref/fate/exif-image-webp | 91 +++---- 17 files changed, 631 insertions(+), 230 deletions(-) create mode 100644 libavcodec/exif_internal.h diff --git a/fftools/ffprobe.c b/fftools/ffprobe.c index bf5ebe3ce0..cdd7af6e82 100644 --- a/fftools/ffprobe.c +++ b/fftools/ffprobe.c @@ -32,6 +32,7 @@ #include "libavformat/avformat.h" #include "libavformat/version.h" #include "libavcodec/avcodec.h" +#include "libavcodec/exif.h" #include "libavcodec/version.h" #include "libavutil/ambient_viewing_environment.h" #include "libavutil/avassert.h" @@ -2040,19 +2041,30 @@ static void writer_register_all(void) memset( (ptr) + (cur_n), 0, ((new_n) - (cur_n)) * sizeof(*(ptr)) ); \ } -static inline int show_tags(WriterContext *w, AVDictionary *tags, int section_id) +static inline int show_dict(WriterContext *w, const AVDictionary *tags) { const AVDictionaryEntry *tag = NULL; int ret = 0; - if (!tags) return 0; - writer_print_section_header(w, NULL, section_id); - while ((tag = av_dict_iterate(tags, tag))) { - if ((ret = print_str_validate(tag->key, tag->value)) < 0) + ret = print_str_validate(tag->key, tag->value); + if (ret < 0) break; } + return ret; +} + +static inline int show_tags(WriterContext *w, const AVDictionary *tags, int section_id) +{ + int ret; + + if (!tags) + return 0; + writer_print_section_header(w, NULL, section_id); + + ret = show_dict(w, tags); + writer_print_section_footer(w); return ret; @@ -2920,6 +2932,11 @@ static void print_frame_side_data(WriterContext *w, } else if (sd->type == AV_FRAME_DATA_FILM_GRAIN_PARAMS) { AVFilmGrainParams *fgp = (AVFilmGrainParams *)sd->data; print_film_grain_params(w, fgp); + } else if (sd->type == AV_FRAME_DATA_EXIF) { + AVDictionary *dict = NULL; + int ret = av_exif_parse_buffer(NULL, sd->data, sd->size, &dict, AV_EXIF_PARSE_TIFF_HEADER); + if (ret >= 0) + show_dict(w, dict); } writer_print_section_footer(w); } diff --git a/libavcodec/Makefile b/libavcodec/Makefile index 502be8b09b..4736126b75 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -16,6 +16,7 @@ HEADERS = ac3_parser.h \ dirac.h \ dv_profile.h \ dxva2.h \ + exif.h \ jni.h \ mediacodec.h \ packet.h \ diff --git a/libavcodec/exif.c b/libavcodec/exif.c index 959d114d09..8442e9f174 100644 --- a/libavcodec/exif.c +++ b/libavcodec/exif.c @@ -1,6 +1,7 @@ /* * EXIF metadata parser * Copyright (c) 2013 Thilo Borgmann + * Copyright (c) 2024 Leo Izen * * This file is part of FFmpeg. * @@ -23,12 +24,17 @@ * @file * EXIF metadata parser * @author Thilo Borgmann + * @author Leo Izen */ -#include "exif.h" +#include "libavutil/attributes.h" +#include "libavutil/display.h" + +#include "exif_internal.h" #include "tiff_common.h" #define EXIF_TAG_NAME_LENGTH 32 +#define MAKERNOTE_TAG 0x927c struct exif_tag { char name[EXIF_TAG_NAME_LENGTH]; @@ -175,11 +181,6 @@ static int exif_add_metadata(void *logctx, int count, int type, AVDictionary **metadata) { switch(type) { - case 0: - av_log(logctx, AV_LOG_WARNING, - "Invalid TIFF tag type 0 found for %s with size %d\n", - name, count); - return 0; case TIFF_DOUBLE : return ff_tadd_doubles_metadata(count, name, sep, gb, le, metadata); case TIFF_SSHORT : return ff_tadd_shorts_metadata(count, name, sep, gb, le, 1, metadata); case TIFF_SHORT : return ff_tadd_shorts_metadata(count, name, sep, gb, le, 0, metadata); @@ -192,12 +193,20 @@ static int exif_add_metadata(void *logctx, int count, int type, case TIFF_SLONG : case TIFF_LONG : return ff_tadd_long_metadata(count, name, sep, gb, le, metadata); default: - avpriv_request_sample(logctx, "TIFF tag type (%u)", type); + av_log(logctx, AV_LOG_WARNING, + "Invalid TIFF tag type %d found for %s with size %d\n", type, name, count); return 0; }; } +static int exif_parse_ifd_list(void *logctx, GetByteContext *gb, int le, + int depth, AVDictionary **metadata); +/** + * Decodes a tag and stores it in the dictionary **metadata. + * GetByteContext *gbytes is seeked to the next tag. + * If the tag is an IFD, it is recursively decoded. + */ static int exif_decode_tag(void *logctx, GetByteContext *gbytes, int le, int depth, AVDictionary **metadata) { @@ -220,7 +229,7 @@ static int exif_decode_tag(void *logctx, GetByteContext *gbytes, int le, // store metadata or proceed with next IFD ret = ff_tis_ifd(id); if (ret) { - ret = ff_exif_decode_ifd(logctx, gbytes, le, depth + 1, metadata); + ret = exif_parse_ifd_list(logctx, gbytes, le, depth + 1, metadata); } else { const char *name = exif_get_tag_name(id); char buf[7]; @@ -239,35 +248,374 @@ static int exif_decode_tag(void *logctx, GetByteContext *gbytes, int le, return ret; } +static const uint8_t casio_header[] = { + 'Q', 'V', 'C', 0, 0, 0, +}; + +static const uint8_t fuji_header[] = { + 'F', 'U', 'J', 'I', +}; + +static const uint8_t nikon_header[] = { + 'N', 'i', 'k', 'o', 'n', 0, +}; -int ff_exif_decode_ifd(void *logctx, GetByteContext *gbytes, - int le, int depth, AVDictionary **metadata) +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 panosonic_header[] = { + 'P', 'a', 'n', 'o', 's', 'o', 'n', 'i', 'c', 0, 0, 0, +}; + +static const uint8_t aoc_header[] = { + 'A', 'O', 'C', 0, +}; + +static const uint8_t sigma_header[] = { + 'S', 'I', 'G', 'M', 'A', 0, 0, 0, +}; + +static const uint8_t foveon_header[] = { + 'F', 'O', 'V', 'E', 'O', 'N', 0, 0, +}; + +static const uint8_t sony_header[] = { + 'S', 'O', 'N', 'Y', ' ', 'D', 'S', 'C', ' ', 0, 0, 0, +}; + +/** + * Get the offset for IFD inside MakerNote. + * + * Returns -1 if this makernote isn't an IFD or if it contains + * and IFD that's relative to the start of the MakerNote, otherwise + * returns the offset. + * + * Most manufacturers use IFDs for these with no header (e.g. Canon) + * so the default is to assume the MakerNote is an IFD with offset 0. + */ +static int exif_get_makernote_offset(GetByteContext *gb) { + if (!memcmp(gb->buffer, casio_header, sizeof(casio_header))) { + return -1; + } else if (!memcmp(gb->buffer, fuji_header, sizeof(fuji_header))) { + return -1; + } else if (!memcmp(gb->buffer, olympus2_header, sizeof(olympus2_header))) { + return -1; + } else if (!memcmp(gb->buffer, olympus1_header, sizeof(olympus1_header))) { + return 8; + } else 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) == 0x49492a00 || AV_RB32(gb->buffer + 10) == 0x4d4d002a) + return -1; + return 8; + } else if (!memcmp(gb->buffer, panosonic_header, sizeof(panosonic_header))) { + return 12; + } else if (!memcmp(gb->buffer, aoc_header, sizeof(aoc_header))) { + return 6; + } else if (!memcmp(gb->buffer, sigma_header, sizeof(sigma_header))) { + return 10; + } else if (!memcmp(gb->buffer, foveon_header, sizeof(foveon_header))) { + return 10; + } else if (!memcmp(gb->buffer, sony_header, sizeof(sony_header))) { + return 12; + } + + return 0; +} + +/** + * Calculate the size of an IFD, so exif_collect_ifd_list knows how big of a space it has + * to allocate. It does so by adding the tag sizes, and recursively adding the sizes of + * IFD tags encounted. + */ +static int exif_get_collect_size(void *logctx, GetByteContext *gb, int le, int depth) { - int i, ret; - int entries; + int entries, total_size = 2; + GetByteContext gbytes; - entries = ff_tget_short(gbytes, le); + if (depth > 2) + return 0; - if (bytestream2_get_bytes_left(gbytes) < entries * 12) { + gbytes = *gb; + entries = ff_tget_short(&gbytes, le); + if (bytestream2_get_bytes_left(&gbytes) < entries * 12) return AVERROR_INVALIDDATA; + + for (int i = 0; i < entries; i++) { + int cur_pos, makernote_ifd = -1; + unsigned id, count; + enum TiffTypes type; + ff_tread_tag(&gbytes, le, &id, &type, &count, &cur_pos); + if (!bytestream2_tell(&gbytes)) { + bytestream2_seek(&gbytes, cur_pos, SEEK_SET); + continue; + } + if (id == MAKERNOTE_TAG) + makernote_ifd = exif_get_makernote_offset(&gbytes); + if (id != MAKERNOTE_TAG && ff_tis_ifd(id) || makernote_ifd >= 0) { + int ret, makernote_off = makernote_ifd >= 0 ? makernote_ifd : 0; + bytestream2_seek(&gbytes, makernote_off, SEEK_CUR); + ret = exif_get_collect_size(logctx, &gbytes, le, depth + 1); + if (ret < 0) + return ret; + total_size += ret + 12 + makernote_off; + } else { + int payload_size = type == TIFF_STRING ? count : count * type_sizes[type]; + if (payload_size > 4) + total_size += 12 + payload_size; + else + total_size += 12; + } + bytestream2_seek(&gbytes, cur_pos, SEEK_SET); } - for (i = 0; i < entries; i++) { - if ((ret = exif_decode_tag(logctx, gbytes, le, depth, metadata)) < 0) { - return ret; + return total_size; +} + +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); +} + +/** + * Takes an IFD and collects it into a compact buffer with initial offset of 0. + * This is useful if ExifTag occurs inside TIFF, or another file with initial + * nonzero offsets. + */ +static int exif_collect_ifd_list(void *logctx, GetByteContext *gb, int le, int depth, PutByteContext *pb) +{ + int entries, ret = 0, offset; + GetByteContext gbytes; + + if (depth > 2) + return 0; + + gbytes = *gb; + entries = ff_tget_short(&gbytes, le); + if (bytestream2_get_bytes_left(&gbytes) < entries * 12) + return AVERROR_INVALIDDATA; + + tput16(pb, le, entries); + offset = bytestream2_tell_p(pb) + entries * 12; + for (int i = 0; i < entries; i++) { + int cur_pos, makernote_ifd = -1; + unsigned id, count; + enum TiffTypes type; + ff_tread_tag(&gbytes, le, &id, &type, &count, &cur_pos); + if (!bytestream2_tell(&gbytes)) { + bytestream2_seek(&gbytes, cur_pos, SEEK_SET); + continue; + } + if (bytestream2_get_bytes_left_p(pb) < 12) + return AVERROR_BUFFER_TOO_SMALL; + tput16(pb, le, id); + tput16(pb, le, type); + tput32(pb, le, count); + if (id == MAKERNOTE_TAG) + makernote_ifd = exif_get_makernote_offset(&gbytes); + if (id != MAKERNOTE_TAG && ff_tis_ifd(id) || makernote_ifd >= 0) { + int tell = bytestream2_tell_p(pb); + int makernote_off = makernote_ifd >= 0 ? makernote_ifd : 0; + tput32(pb, le, offset); + bytestream2_seek_p(pb, offset, SEEK_SET); + if (makernote_off) + bytestream2_copy_buffer(pb, &gbytes, makernote_off); + ret = exif_collect_ifd_list(logctx, &gbytes, le, depth + 1, pb); + if (ret < 0) + return ret; + offset += ret + makernote_off; + bytestream2_seek_p(pb, tell + 4, SEEK_SET); + } else { + int payload_size = type == TIFF_STRING ? count : count * type_sizes[type]; + if (payload_size > 4) { + int tell = bytestream2_tell_p(pb); + tput32(pb, le, offset); + bytestream2_seek_p(pb, offset, SEEK_SET); + if (bytestream2_get_bytes_left(&gbytes) < payload_size) + return AVERROR_INVALIDDATA; + bytestream2_put_buffer(pb, gbytes.buffer, payload_size); + offset += payload_size; + bytestream2_seek_p(pb, tell + 4, SEEK_SET); + } else { + bytestream2_put_ne32(pb, bytestream2_get_ne32(&gbytes)); + } } + bytestream2_seek(&gbytes, cur_pos, SEEK_SET); + } + + return offset; +} + +int ff_exif_collect_ifd(void *logctx, GetByteContext *gb, int le, AVBufferRef **buffer) +{ + AVBufferRef *ref = NULL; + int total_size, ret; + PutByteContext pb; + if (!buffer) + return 0; + + total_size = exif_get_collect_size(logctx, gb, le, 0); + if (total_size <= 0) + return total_size; + total_size += 8; + ref = av_buffer_alloc(total_size); + if (!ref) + return AVERROR(ENOMEM); + bytestream2_init_writer(&pb, ref->data, total_size); + bytestream2_put_be32(&pb, le ? 0x49492a00 : 0x4d4d002a); + tput32(&pb, le, 8); + + ret = exif_collect_ifd_list(logctx, gb, le, 0, &pb); + if (ret < 0) + av_buffer_unref(&ref); + + *buffer = ref; + return ret; +} + +/** + * Parse an IFD and read it into the dictonary **metadata. + */ +static int exif_parse_ifd_list(void *logctx, GetByteContext *gb, int le, + int depth, AVDictionary **metadata) +{ + int entries = ff_tget_short(gb, le); + if (bytestream2_get_bytes_left(gb) < entries * 12) + return AVERROR_INVALIDDATA; + + for (int i = 0; i < entries; i++) { + int ret = exif_decode_tag(logctx, gb, le, depth, metadata); + if (ret < 0) + return ret; } // return next IDF offset or 0x000000000 or a value < 0 for failure - return ff_tget_long(gbytes, le); + return ff_tget_long(gb, le); } -int avpriv_exif_decode_ifd(void *logctx, const uint8_t *buf, int size, - int le, int depth, AVDictionary **metadata) +int av_exif_parse_buffer(void *logctx, const uint8_t *buf, size_t size, + AVDictionary **metadata, enum AVExifParseMode parse_mode) { - GetByteContext gb; + int ret, le; + GetByteContext gbytes; + if (size > INT_MAX) + return AVERROR(EINVAL); + bytestream2_init(&gbytes, buf, size); + if (parse_mode == AV_EXIF_PARSE_TIFF_HEADER) { + int ifd_offset; + // 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\n"); + return ret; + } + bytestream2_seek(&gbytes, ifd_offset, SEEK_SET); + } else { + le = parse_mode == AV_EXIF_ASSUME_LE; + } + + // read 0th IFD and store the metadata + // (return values > 0 indicate the presence of subimage metadata) + ret = exif_parse_ifd_list(logctx, &gbytes, le, 0, metadata); + if (ret < 0) { + av_log(logctx, AV_LOG_ERROR, "error decoding EXIF data\n"); + return ret; + } + + return bytestream2_tell(&gbytes); +} + +/** + * Attach an AV_FRAME_DATA_DISPLAYMATRIX based on the orientation string value. + */ +static int attach_displaymatrix(void *logctx, AVFrame *frame, const char *value) +{ + char *endptr; + AVFrameSideData *sd; + long orientation = strtol(value, &endptr, 0); + int32_t *matrix; + /* invalid string */ + if (*endptr || endptr == value) + return 0; + /* invalid orientation */ + if (orientation < 2 || orientation > 8) + return 0; + sd = av_frame_new_side_data(frame, AV_FRAME_DATA_DISPLAYMATRIX, sizeof(int32_t) * 9); + if (!sd) { + av_log(logctx, AV_LOG_ERROR, "Could not allocate frame side data\n"); + return AVERROR(ENOMEM); + } + matrix = (int32_t *) sd->data; + + switch (orientation) { + 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: + av_assert0(0); + } - bytestream2_init(&gb, buf, size); + return 0; +} - return ff_exif_decode_ifd(logctx, &gb, le, depth, metadata); +int ff_exif_attach(void *logctx, AVFrame *frame, AVBufferRef **data) +{ + const AVDictionaryEntry *e = NULL; + int ret; + AVDictionary *m = NULL; + AVBufferRef *buffer = *data; + AVFrameSideData *sd = av_frame_new_side_data_from_buf(frame, AV_FRAME_DATA_EXIF, buffer); + if (!sd) + return AVERROR(ENOMEM); + *data = NULL; + ret = av_exif_parse_buffer(logctx, buffer->data, buffer->size, &m, AV_EXIF_PARSE_TIFF_HEADER); + if (ret < 0) + return ret; + + if ((e = av_dict_get(m, "Orientation", e, AV_DICT_IGNORE_SUFFIX))) { + ret = attach_displaymatrix(logctx, frame, e->value); + if (ret < 0) + return ret; + } + + return 0; +} + +attribute_deprecated +int avpriv_exif_decode_ifd(void *logctx, const uint8_t *buf, int size, + int le, int depth, AVDictionary **metadata) +{ + return av_exif_parse_buffer(logctx, buf, size, metadata, le ? AV_EXIF_ASSUME_LE : AV_EXIF_ASSUME_BE); } diff --git a/libavcodec/exif.h b/libavcodec/exif.h index f70d21391a..cf54be68ba 100644 --- a/libavcodec/exif.h +++ b/libavcodec/exif.h @@ -1,6 +1,7 @@ /* * EXIF metadata parser * Copyright (c) 2013 Thilo Borgmann + * Copyright (c) 2024 Leo Izen * * This file is part of FFmpeg. * @@ -23,21 +24,40 @@ * @file * EXIF metadata parser * @author Thilo Borgmann + * @author Leo Izen */ #ifndef AVCODEC_EXIF_H #define AVCODEC_EXIF_H +#include #include + +#include "libavutil/attributes.h" #include "libavutil/dict.h" -#include "bytestream.h" -/** Recursively decodes all IFD's and - * adds included TAGS into the metadata dictionary. */ +enum AVExifParseMode { + AV_EXIF_PARSE_TIFF_HEADER, + AV_EXIF_ASSUME_LE, + AV_EXIF_ASSUME_BE, +}; + +/** + * Parse an EXIF metadata buffer into the AVDictionary **metadata. + * + * @param logctx A log context for error logging. + * @param buf This buffer contains an EXIF blob. + * @param size The size of the buffer. + * @param metadata A metadata dictionary, into which the key/value tags are written. + * @param parse_mode An enum indicating whether to parse the TIFF header, + * or to assume it's LE/BE and skip it. + * @return negative upon failure, otherwise total bytes read from the buffer. + */ +int av_exif_parse_buffer(void *logctx, const uint8_t *data, size_t size, + AVDictionary **metadata, enum AVExifParseMode parse_mode); + +attribute_deprecated int avpriv_exif_decode_ifd(void *logctx, const uint8_t *buf, int size, int le, int depth, AVDictionary **metadata); -int ff_exif_decode_ifd(void *logctx, GetByteContext *gbytes, int le, - int depth, AVDictionary **metadata); - #endif /* AVCODEC_EXIF_H */ diff --git a/libavcodec/exif_internal.h b/libavcodec/exif_internal.h new file mode 100644 index 0000000000..2c788856d6 --- /dev/null +++ b/libavcodec/exif_internal.h @@ -0,0 +1,55 @@ +/* + * EXIF metadata parser - internal functions + * Copyright (c) 2013 Thilo Borgmann + * Copyright (c) 2024 Leo Izen + * + * 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 - internal functions + * @author Thilo Borgmann + * @author Leo Izen + */ + +#ifndef AVCODEC_EXIF_INTERNAL_H +#define AVCODEC_EXIF_INTERNAL_H + +#include "libavutil/buffer.h" +#include "libavutil/frame.h" +#include "bytestream.h" +#include "exif.h" + +/** + * Attach an AVBufferRef containing EXIF metadata to a frame. + * This function allocates the necessary AVFrameSideData and attaches it, + * and also attaches any necessary other sidedata that can be read from the, + * EXIF metadata, such as a display matrix. + */ +int ff_exif_attach(void *logctx, AVFrame *frame, AVBufferRef **data); + +/** + * Collects an IFD into a buffer. **buffer points to an AVBufferRef *, which will + * be allocated by this function. The caller needs to unref the buffer when it is + * done with it. This function also writes the EXIF/TIFF header into the buffer + * based on the endianness, so encoders can pass the buffer as-is. This function can be + * used by TIFF or other codecs that have non-zero IFD offsets on their EXIF metadata. + */ +int ff_exif_collect_ifd(void *logctx, GetByteContext *gb, int le, AVBufferRef **buffer); + +#endif /* AVCODEC_EXIF_INTERNAL_H */ diff --git a/libavcodec/mjpegdec.c b/libavcodec/mjpegdec.c index 86ec58713c..9ed71976df 100644 --- a/libavcodec/mjpegdec.c +++ b/libavcodec/mjpegdec.c @@ -43,6 +43,7 @@ #include "codec_internal.h" #include "copy_block.h" #include "decode.h" +#include "exif_internal.h" #include "hwaccel_internal.h" #include "hwconfig.h" #include "idctdsp.h" @@ -2039,8 +2040,6 @@ static int mjpeg_decode_app(MJpegDecodeContext *s) /* EXIF metadata */ if (s->start_code == APP1 && id == AV_RB32("Exif") && len >= 2) { - GetByteContext gbytes; - int ret, le, ifd_offset, bytes_read; const uint8_t *aligned; skip_bits(&s->gb, 16); // skip padding @@ -2048,27 +2047,12 @@ static int mjpeg_decode_app(MJpegDecodeContext *s) // init byte wise reading aligned = align_get_bits(&s->gb); - bytestream2_init(&gbytes, aligned, len); - - // read TIFF header - ret = ff_tdecode_header(&gbytes, &le, &ifd_offset); - if (ret) { - av_log(s->avctx, AV_LOG_ERROR, "mjpeg: invalid TIFF header in EXIF data\n"); - } else { - bytestream2_seek(&gbytes, ifd_offset, SEEK_SET); - - // read 0th IFD and store the metadata - // (return values > 0 indicate the presence of subimage metadata) - ret = ff_exif_decode_ifd(s->avctx, &gbytes, le, 0, &s->exif_metadata); - if (ret < 0) { - av_log(s->avctx, AV_LOG_ERROR, "mjpeg: error decoding EXIF data\n"); - } - } - - bytes_read = bytestream2_tell(&gbytes); - skip_bits(&s->gb, bytes_read << 3); - len -= bytes_read; - + s->exif_buffer = av_buffer_alloc(len); + if (!s->exif_buffer) + return AVERROR(ENOMEM); + memcpy(s->exif_buffer->data, aligned, len); + skip_bits(&s->gb, len << 3); + len = 0; goto out; } @@ -2380,13 +2364,12 @@ int ff_mjpeg_decode_frame_from_buf(AVCodecContext *avctx, AVFrame *frame, int index; int ret = 0; int is16bit; - AVDictionaryEntry *e = NULL; s->force_pal8 = 0; s->buf_size = buf_size; - av_dict_free(&s->exif_metadata); + av_buffer_unref(&s->exif_buffer); av_freep(&s->stereo3d); s->adobe_transform = -1; @@ -2853,60 +2836,12 @@ the_end: } } - if (e = av_dict_get(s->exif_metadata, "Orientation", e, AV_DICT_IGNORE_SUFFIX)) { - char *value = e->value + strspn(e->value, " \n\t\r"), *endptr; - int orientation = strtol(value, &endptr, 0); - - if (!*endptr) { - AVFrameSideData *sd = NULL; - - if (orientation >= 2 && orientation <= 8) { - int32_t *matrix; - - sd = av_frame_new_side_data(frame, AV_FRAME_DATA_DISPLAYMATRIX, sizeof(int32_t) * 9); - if (!sd) { - av_log(avctx, AV_LOG_ERROR, "Could not allocate frame side data\n"); - return AVERROR(ENOMEM); - } - - matrix = (int32_t *)sd->data; - - switch (orientation) { - 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: - av_assert0(0); - } - } - } + if (s->exif_buffer) { + ret = ff_exif_attach(s->avctx, frame, &s->exif_buffer); + if (ret < 0) + return ret; } - av_dict_copy(&frame->metadata, s->exif_metadata, 0); - av_dict_free(&s->exif_metadata); - if (avctx->codec_id != AV_CODEC_ID_SMVJPEG && (avctx->codec_tag == MKTAG('A', 'V', 'R', 'n') || avctx->codec_tag == MKTAG('A', 'V', 'D', 'J')) && @@ -2961,7 +2896,7 @@ av_cold int ff_mjpeg_decode_end(AVCodecContext *avctx) av_freep(&s->blocks[i]); av_freep(&s->last_nnz[i]); } - av_dict_free(&s->exif_metadata); + av_buffer_unref(&s->exif_buffer); reset_icc_profile(s); diff --git a/libavcodec/mjpegdec.h b/libavcodec/mjpegdec.h index 13c524d597..780381445f 100644 --- a/libavcodec/mjpegdec.h +++ b/libavcodec/mjpegdec.h @@ -138,7 +138,7 @@ typedef struct MJpegDecodeContext { unsigned int ljpeg_buffer_size; int extern_huff; - AVDictionary *exif_metadata; + AVBufferRef *exif_buffer; AVStereo3D *stereo3d; ///!< stereoscopic information (cached, since it is read before frame allocation) diff --git a/libavcodec/tiff.c b/libavcodec/tiff.c index 37b56e9757..45c3ae8c97 100644 --- a/libavcodec/tiff.c +++ b/libavcodec/tiff.c @@ -46,6 +46,7 @@ #include "bytestream.h" #include "codec_internal.h" #include "decode.h" +#include "exif_internal.h" #include "faxcompr.h" #include "lzw.h" #include "tiff.h" @@ -1268,7 +1269,7 @@ static int tiff_decode_tag(TiffContext *s, AVFrame *frame) s->last_tag = tag; off = bytestream2_tell(&s->gb); - if (count == 1) { + if (count == 1 && tag != TIFF_EXIFTAG) { switch (type) { case TIFF_BYTE: case TIFF_SHORT: @@ -1770,6 +1771,22 @@ static int tiff_decode_tag(TiffContext *s, AVFrame *frame) case TIFF_SOFTWARE_NAME: ADD_METADATA(count, "software", NULL); break; + case TIFF_EXIFTAG: { + AVBufferRef *exif = NULL; + int next; + gb_temp = s->gb; + next = ff_exif_collect_ifd(s->avctx, &gb_temp, s->le, &exif); + if (next < 0) + av_log(s->avctx, AV_LOG_ERROR, "Error parsing TIFF exif tags: %d\n", next); + else if (next) + bytestream2_seek(&s->gb, next, SEEK_SET); + if (exif) { + ret = ff_exif_attach(s->avctx, frame, &exif); + if (ret < 0) + return ret; + } + break; + } case DNG_VERSION: if (count == 4) { unsigned int ver[4]; diff --git a/libavcodec/tiff.h b/libavcodec/tiff.h index 12afcfa6e5..2628f9885f 100644 --- a/libavcodec/tiff.h +++ b/libavcodec/tiff.h @@ -94,6 +94,7 @@ enum TiffTags { TIFF_GEO_KEY_DIRECTORY = 0x87AF, TIFF_GEO_DOUBLE_PARAMS = 0x87B0, TIFF_GEO_ASCII_PARAMS = 0x87B1, + TIFF_EXIFTAG = 0x8769, }; /** abridged list of DNG tags */ diff --git a/libavcodec/version.h b/libavcodec/version.h index 7531c6c42a..9b8c267529 100644 --- a/libavcodec/version.h +++ b/libavcodec/version.h @@ -29,7 +29,7 @@ #include "version_major.h" -#define LIBAVCODEC_VERSION_MINOR 14 +#define LIBAVCODEC_VERSION_MINOR 15 #define LIBAVCODEC_VERSION_MICRO 100 #define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \ diff --git a/libavcodec/webp.c b/libavcodec/webp.c index 7c2a5f0111..659f641652 100644 --- a/libavcodec/webp.c +++ b/libavcodec/webp.c @@ -35,6 +35,9 @@ * Exif metadata * ICC profile * + * @author Leo Izen + * Exif metadata + * * Unimplemented: * - Animation * - XMP metadata @@ -48,7 +51,7 @@ #include "bytestream.h" #include "codec_internal.h" #include "decode.h" -#include "exif.h" +#include "exif_internal.h" #include "get_bits.h" #include "thread.h" #include "tiff_common.h" @@ -1452,13 +1455,12 @@ static int webp_decode_frame(AVCodecContext *avctx, AVFrame *p, break; } case MKTAG('E', 'X', 'I', 'F'): { - int le, ifd_offset, exif_offset = bytestream2_tell(&gb); - AVDictionary *exif_metadata = NULL; - GetByteContext exif_gb; + AVBufferRef *buf; if (s->has_exif) { av_log(avctx, AV_LOG_VERBOSE, "Ignoring extra EXIF chunk\n"); - goto exif_end; + bytestream2_skip(&gb, chunk_size); + break; } if (!(vp8x_flags & VP8X_FLAG_EXIF_METADATA)) av_log(avctx, AV_LOG_WARNING, @@ -1466,25 +1468,13 @@ static int webp_decode_frame(AVCodecContext *avctx, AVFrame *p, "VP8X header\n"); s->has_exif = 1; - bytestream2_init(&exif_gb, avpkt->data + exif_offset, - avpkt->size - exif_offset); - if (ff_tdecode_header(&exif_gb, &le, &ifd_offset) < 0) { - av_log(avctx, AV_LOG_ERROR, "invalid TIFF header " - "in Exif data\n"); - goto exif_end; - } - - bytestream2_seek(&exif_gb, ifd_offset, SEEK_SET); - if (ff_exif_decode_ifd(avctx, &exif_gb, le, 0, &exif_metadata) < 0) { - av_log(avctx, AV_LOG_ERROR, "error decoding Exif data\n"); - goto exif_end; - } - - av_dict_copy(&p->metadata, exif_metadata, 0); - -exif_end: - av_dict_free(&exif_metadata); - bytestream2_skip(&gb, chunk_size); + buf = av_buffer_alloc(chunk_size); + if (!buf) + return AVERROR(ENOMEM); + bytestream2_get_buffer(&gb, buf->data, chunk_size); + ret = ff_exif_attach(avctx, p, &buf); + if (ret < 0) + return ret; break; } case MKTAG('I', 'C', 'C', 'P'): { diff --git a/libavformat/avidec.c b/libavformat/avidec.c index 1ae09efc15..1cea72471f 100644 --- a/libavformat/avidec.c +++ b/libavformat/avidec.c @@ -433,8 +433,8 @@ static int avi_extract_stream_metadata(AVFormatContext *s, AVStream *st) offset = bytestream2_tell(&gb); // decode EXIF tags from IFD, AVI is always little-endian - return avpriv_exif_decode_ifd(s, data + offset, data_size - offset, - 1, 0, &st->metadata); + return av_exif_parse_buffer(s, data + offset, data_size - offset, + &st->metadata, AV_EXIF_ASSUME_LE); break; case MKTAG('C', 'A', 'S', 'I'): avpriv_request_sample(s, "RIFF stream data tag type CASI (%u)", tag); diff --git a/libavutil/frame.c b/libavutil/frame.c index 5cbfc6a48b..f668ed87fd 100644 --- a/libavutil/frame.c +++ b/libavutil/frame.c @@ -56,6 +56,8 @@ static const AVSideDataDescriptor sd_props[] = { [AV_FRAME_DATA_SPHERICAL] = { "Spherical Mapping", AV_SIDE_DATA_PROP_GLOBAL }, [AV_FRAME_DATA_ICC_PROFILE] = { "ICC profile", AV_SIDE_DATA_PROP_GLOBAL }, [AV_FRAME_DATA_SEI_UNREGISTERED] = { "H.26[45] User Data Unregistered SEI message", AV_SIDE_DATA_PROP_MULTI }, + [AV_FRAME_DATA_EXIF] = { "EXIF metadata", + AV_SIDE_DATA_PROP_GLOBAL }, }; static void get_frame_defaults(AVFrame *frame) diff --git a/libavutil/frame.h b/libavutil/frame.h index 60bb966f8b..3614b412bb 100644 --- a/libavutil/frame.h +++ b/libavutil/frame.h @@ -228,6 +228,12 @@ enum AVFrameSideDataType { * encoding. */ AV_FRAME_DATA_VIDEO_HINT, + + /** + * Extensible image file format metadata. The payload is a buffer containing + * EXIF metadata, starting with either 49 49 2a 00, or 4d 4d 00 2a. + */ + AV_FRAME_DATA_EXIF, }; enum AVActiveFormatDescription { diff --git a/tests/ref/fate/exif-image-embedded b/tests/ref/fate/exif-image-embedded index 98b6ec5a44..1edda0fc92 100644 --- a/tests/ref/fate/exif-image-embedded +++ b/tests/ref/fate/exif-image-embedded @@ -29,8 +29,11 @@ color_space=bt470bg color_primaries=unknown color_transfer=unknown chroma_location=center -TAG:UserComment=AppleMark +[SIDE_DATA] +side_data_type=EXIF metadata +UserComment=AppleMark +[/SIDE_DATA] [/FRAME] [FRAME] media_type=audio diff --git a/tests/ref/fate/exif-image-jpg b/tests/ref/fate/exif-image-jpg index 2e314078da..bcc6184085 100644 --- a/tests/ref/fate/exif-image-jpg +++ b/tests/ref/fate/exif-image-jpg @@ -29,31 +29,33 @@ color_space=bt470bg color_primaries=unknown color_transfer=unknown chroma_location=center -TAG:ImageDescription= -TAG:Make=Canon -TAG:Model=Canon PowerShot SX200 IS -TAG:Orientation= 1 -TAG:XResolution= 180:1 -TAG:YResolution= 180:1 -TAG:ResolutionUnit= 2 -TAG:DateTime=2013:07:18 13:12:03 -TAG:YCbCrPositioning= 2 -TAG:ExposureTime= 1:1250 -TAG:FNumber= 40:10 -TAG:ISOSpeedRatings= 160 -TAG:ExifVersion= 48, 50, 50, 49 -TAG:DateTimeOriginal=2013:07:18 13:12:03 -TAG:DateTimeDigitized=2013:07:18 13:12:03 -TAG:ComponentsConfiguration= 1, 2, 3, 0 -TAG:CompressedBitsPerPixel= 3:1 -TAG:ShutterSpeedValue= 329:32 -TAG:ApertureValue= 128:32 -TAG:ExposureBiasValue= 0:3 -TAG:MaxApertureValue= 113:32 -TAG:MeteringMode= 5 -TAG:Flash= 16 -TAG:FocalLength= 5000:1000 -TAG:MakerNote= +[SIDE_DATA] +side_data_type=EXIF metadata +ImageDescription= +Make=Canon +Model=Canon PowerShot SX200 IS +Orientation= 1 +XResolution= 180:1 +YResolution= 180:1 +ResolutionUnit= 2 +DateTime=2013:07:18 13:12:03 +YCbCrPositioning= 2 +ExposureTime= 1:1250 +FNumber= 40:10 +ISOSpeedRatings= 160 +ExifVersion= 48, 50, 50, 49 +DateTimeOriginal=2013:07:18 13:12:03 +DateTimeDigitized=2013:07:18 13:12:03 +ComponentsConfiguration= 1, 2, 3, 0 +CompressedBitsPerPixel= 3:1 +ShutterSpeedValue= 329:32 +ApertureValue= 128:32 +ExposureBiasValue= 0:3 +MaxApertureValue= 113:32 +MeteringMode= 5 +Flash= 16 +FocalLength= 5000:1000 +MakerNote= 25, 0, 1, 0, 3, 0, 48, 0, 0, 0, 28, 4, 0, 0, 2, 0 3, 0, 4, 0, 0, 0, 124, 4, 0, 0, 3, 0, 3, 0, 4, 0 0, 0, 132, 4, 0, 0, 4, 0, 3, 0, 34, 0, 0, 0, 140, 4 @@ -195,7 +197,7 @@ TAG:MakerNote= 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0 255, 255, 0, 0, 0, 0, 239, 154, 237, 228, 191, 235, 20, 171, 30, 6 2, 129, 88, 251, 56, 49, 73, 73, 42, 0, 222, 2, 0, 0 -TAG:UserComment= +UserComment= 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 @@ -213,22 +215,23 @@ TAG:UserComment= 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 0, 0, 0, 0, 0, 0, 0, 0 -TAG:FlashpixVersion= 48, 49, 48, 48 -TAG:ColorSpace= 1 -TAG:PixelXDimension= 4000 -TAG:PixelYDimension= 2248 -TAG:GPSLatitudeRef=R98 -TAG:GPSLatitude= 48, 49, 48, 48 -TAG:0x1001= 4000 -TAG:0x1002= 2248 -TAG:FocalPlaneXResolution=4000000:244 -TAG:FocalPlaneYResolution=2248000:183 -TAG:FocalPlaneResolutionUnit= 2 -TAG:SensingMethod= 2 -TAG:FileSource= 3 -TAG:CustomRendered= 0 -TAG:ExposureMode= 0 -TAG:WhiteBalance= 0 -TAG:DigitalZoomRatio= 4000:4000 -TAG:SceneCaptureType= 0 +FlashpixVersion= 48, 49, 48, 48 +ColorSpace= 1 +PixelXDimension= 4000 +PixelYDimension= 2248 +GPSLatitudeRef=R98 +GPSLatitude= 48, 49, 48, 48 +0x1001= 4000 +0x1002= 2248 +FocalPlaneXResolution=4000000:244 +FocalPlaneYResolution=2248000:183 +FocalPlaneResolutionUnit= 2 +SensingMethod= 2 +FileSource= 3 +CustomRendered= 0 +ExposureMode= 0 +WhiteBalance= 0 +DigitalZoomRatio= 4000:4000 +SceneCaptureType= 0 +[/SIDE_DATA] [/FRAME] diff --git a/tests/ref/fate/exif-image-webp b/tests/ref/fate/exif-image-webp index 73560e8ba0..d1fd7ea4e3 100644 --- a/tests/ref/fate/exif-image-webp +++ b/tests/ref/fate/exif-image-webp @@ -29,31 +29,33 @@ color_space=bt470bg color_primaries=unknown color_transfer=unknown chroma_location=unspecified -TAG:ImageDescription= -TAG:Make=Canon -TAG:Model=Canon PowerShot SX200 IS -TAG:Orientation= 1 -TAG:XResolution= 180:1 -TAG:YResolution= 180:1 -TAG:ResolutionUnit= 2 -TAG:DateTime=2013:07:18 13:12:03 -TAG:YCbCrPositioning= 2 -TAG:ExposureTime= 1:1250 -TAG:FNumber= 40:10 -TAG:ISOSpeedRatings= 160 -TAG:ExifVersion= 48, 50, 50, 49 -TAG:DateTimeOriginal=2013:07:18 13:12:03 -TAG:DateTimeDigitized=2013:07:18 13:12:03 -TAG:ComponentsConfiguration= 1, 2, 3, 0 -TAG:CompressedBitsPerPixel= 3:1 -TAG:ShutterSpeedValue= 329:32 -TAG:ApertureValue= 128:32 -TAG:ExposureBiasValue= 0:3 -TAG:MaxApertureValue= 113:32 -TAG:MeteringMode= 5 -TAG:Flash= 16 -TAG:FocalLength= 5000:1000 -TAG:MakerNote= +[SIDE_DATA] +side_data_type=EXIF metadata +ImageDescription= +Make=Canon +Model=Canon PowerShot SX200 IS +Orientation= 1 +XResolution= 180:1 +YResolution= 180:1 +ResolutionUnit= 2 +DateTime=2013:07:18 13:12:03 +YCbCrPositioning= 2 +ExposureTime= 1:1250 +FNumber= 40:10 +ISOSpeedRatings= 160 +ExifVersion= 48, 50, 50, 49 +DateTimeOriginal=2013:07:18 13:12:03 +DateTimeDigitized=2013:07:18 13:12:03 +ComponentsConfiguration= 1, 2, 3, 0 +CompressedBitsPerPixel= 3:1 +ShutterSpeedValue= 329:32 +ApertureValue= 128:32 +ExposureBiasValue= 0:3 +MaxApertureValue= 113:32 +MeteringMode= 5 +Flash= 16 +FocalLength= 5000:1000 +MakerNote= 25, 0, 1, 0, 3, 0, 48, 0, 0, 0, 28, 4, 0, 0, 2, 0 3, 0, 4, 0, 0, 0, 124, 4, 0, 0, 3, 0, 3, 0, 4, 0 0, 0, 132, 4, 0, 0, 4, 0, 3, 0, 34, 0, 0, 0, 140, 4 @@ -195,7 +197,7 @@ TAG:MakerNote= 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0 255, 255, 0, 0, 0, 0, 239, 154, 237, 228, 191, 235, 20, 171, 30, 6 2, 129, 88, 251, 56, 49, 73, 73, 42, 0, 222, 2, 0, 0 -TAG:UserComment= +UserComment= 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 @@ -213,22 +215,23 @@ TAG:UserComment= 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 0, 0, 0, 0, 0, 0, 0, 0 -TAG:FlashpixVersion= 48, 49, 48, 48 -TAG:ColorSpace= 1 -TAG:PixelXDimension= 4000 -TAG:PixelYDimension= 2248 -TAG:GPSLatitudeRef=R98 -TAG:GPSLatitude= 48, 49, 48, 48 -TAG:0x1001= 4000 -TAG:0x1002= 2248 -TAG:FocalPlaneXResolution=4000000:244 -TAG:FocalPlaneYResolution=2248000:183 -TAG:FocalPlaneResolutionUnit= 2 -TAG:SensingMethod= 2 -TAG:FileSource= 3 -TAG:CustomRendered= 0 -TAG:ExposureMode= 0 -TAG:WhiteBalance= 0 -TAG:DigitalZoomRatio= 4000:4000 -TAG:SceneCaptureType= 0 +FlashpixVersion= 48, 49, 48, 48 +ColorSpace= 1 +PixelXDimension= 4000 +PixelYDimension= 2248 +GPSLatitudeRef=R98 +GPSLatitude= 48, 49, 48, 48 +0x1001= 4000 +0x1002= 2248 +FocalPlaneXResolution=4000000:244 +FocalPlaneYResolution=2248000:183 +FocalPlaneResolutionUnit= 2 +SensingMethod= 2 +FileSource= 3 +CustomRendered= 0 +ExposureMode= 0 +WhiteBalance= 0 +DigitalZoomRatio= 4000:4000 +SceneCaptureType= 0 +[/SIDE_DATA] [/FRAME]