Repository: AOMediaCodec/libavif (2026-07-15T15:22:30Z)

Issue:
Bring `ignore-xmp/exif` to avifgainmaputil
Please bring `--ignore-xmp` and `--ignore-exif` to avifgainmaputil, as it does in avifenc. Thanks!


Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 649c6f6c50..3ac57ac54c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@ The changes are relative to the previous release, unless the baseline is specifi
 * Add the ignoreICC option to avifDecoder
 * avifenc: add --ignore-alpha flag to discard alpha channel on encode
 * avifgainmaputil: add --ignore-alpha flag to discard alpha channel
+* avifgainmaputil: add --ignore-exif and --ignore-xmp flags
 
 ### Changed since 1.4.2
 
diff --git a/apps/avifgainmaputil/combine_command.cc b/apps/avifgainmaputil/combine_command.cc
index 420a1938a5..b58b73b958 100644
--- a/apps/avifgainmaputil/combine_command.cc
+++ b/apps/avifgainmaputil/combine_command.cc
@@ -98,7 +98,8 @@ avifResult CombineCommand::Run() {
   }
   avifResult result = ReadImage(
       base_image.get(), arg_base_filename_, pixel_format, arg_image_read_.depth,
-      arg_image_read_.ignore_profile, arg_image_read_.ignore_alpha,
+      arg_image_read_.ignore_profile, arg_image_read_.ignore_exif,
+      arg_image_read_.ignore_xmp, arg_image_read_.ignore_alpha,
       /*ignore_gain_map=*/true, arg_jobs_.jobs.value());
   if (result != AVIF_RESULT_OK) {
     std::cout << "Failed to read base image: " << avifResultToString(result)
@@ -117,6 +118,7 @@ avifResult CombineCommand::Run() {
   result =
       ReadImage(alternate_image.get(), arg_alternate_filename_, pixel_format,
                 arg_image_read_.depth, arg_image_read_.ignore_profile,
+                arg_image_read_.ignore_exif, arg_image_read_.ignore_xmp,
                 arg_image_read_.ignore_alpha, /*ignore_gain_map=*/true,
                 arg_jobs_.jobs.value());
   if (result != AVIF_RESULT_OK) {
diff --git a/apps/avifgainmaputil/convert_command.cc b/apps/avifgainmaputil/convert_command.cc
index 54e4744731..b6ba6b3bf7 100644
--- a/apps/avifgainmaputil/convert_command.cc
+++ b/apps/avifgainmaputil/convert_command.cc
@@ -62,6 +62,7 @@ avifResult ConvertCommand::Run() {
   avifResult result =
       ReadImage(image.get(), arg_input_filename_.value(), pixel_format,
                 arg_image_read_.depth, arg_image_read_.ignore_profile,
+                arg_image_read_.ignore_exif, arg_image_read_.ignore_xmp,
                 arg_image_read_.ignore_alpha, /*ignore_gain_map=*/false,
                 arg_jobs_.jobs.value());
   if (result != AVIF_RESULT_OK) {
diff --git a/apps/avifgainmaputil/imageio.cc b/apps/avifgainmaputil/imageio.cc
index 5e2fc53eb8..97a2b6b277 100644
--- a/apps/avifgainmaputil/imageio.cc
+++ b/apps/avifgainmaputil/imageio.cc
@@ -191,8 +191,8 @@ avifResult WriteAvifGrid(const avifImage* image, int grid_cols, int grid_rows,
 
 avifResult ReadImage(avifImage* image, const std::string& input_filename,
                      avifPixelFormat requested_format, uint32_t requested_depth,
-                     bool ignore_profile, bool ignore_alpha,
-                     bool ignore_gain_map, int jobs) {
+                     bool ignore_profile, bool ignore_exif, bool ignore_xmp,
+                     bool ignore_alpha, bool ignore_gain_map, int jobs) {
   avifAppFileFormat input_format = avifGuessFileFormat(input_filename.c_str());
   if (input_format == AVIF_APP_FILE_FORMAT_UNKNOWN) {
     std::cerr << "Cannot determine input format: " << input_filename;
@@ -207,6 +207,8 @@ avifResult ReadImage(avifImage* image, const std::string& input_filename,
       decoder->imageContentToDecode |= AVIF_IMAGE_CONTENT_GAIN_MAP;
     }
     decoder->ignoreICC = ignore_profile;
+    decoder->ignoreExif = ignore_exif;
+    decoder->ignoreXMP = ignore_xmp;
     avifResult result = ReadAvif(decoder.get(), input_filename);
     if (result != AVIF_RESULT_OK) {
       return result;
@@ -242,9 +244,9 @@ avifResult ReadImage(avifImage* image, const std::string& input_filename,
     const avifAppFileFormat file_format = avifReadImage(
         input_filename.c_str(), AVIF_APP_FILE_FORMAT_UNKNOWN /* guess format */,
         requested_format, static_cast<int>(requested_depth),
-        AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC, ignore_profile,
-        /*ignoreExif=*/false, /*ignoreXMP=*/false, ignore_alpha,
-        ignore_gain_map, AVIF_DEFAULT_IMAGE_SIZE_LIMIT, image,
+        AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC, ignore_profile, ignore_exif,
+        ignore_xmp, ignore_alpha, ignore_gain_map,
+        AVIF_DEFAULT_IMAGE_SIZE_LIMIT, image,
         /*outDepth=*/nullptr,
         /*sourceTiming=*/nullptr, /*frameIter=*/nullptr);
     if (file_format == AVIF_APP_FILE_FORMAT_UNKNOWN) {
diff --git a/apps/avifgainmaputil/imageio.h b/apps/avifgainmaputil/imageio.h
index 48d02751a9..74dcaccb9e 100644
--- a/apps/avifgainmaputil/imageio.h
+++ b/apps/avifgainmaputil/imageio.h
@@ -19,8 +19,8 @@ avifResult WriteImage(const avifImage* image, int grid_cols, int grid_rows,
 // Reads an image in any of the supported formats. Ignores any gain map.
 avifResult ReadImage(avifImage* image, const std::string& input_filename,
                      avifPixelFormat requested_format, uint32_t requested_depth,
-                     bool ignore_profile, bool ignore_alpha,
-                     bool ignore_gain_map, int jobs);
+                     bool ignore_profile, bool ignore_exif, bool ignore_xmp,
+                     bool ignore_alpha, bool ignore_gain_map, int jobs);
 
 // Writes an image in avif format given a pre-configured encoder.
 avifResult WriteAvif(const avifImage* image, avifEncoder* encoder,
diff --git a/apps/avifgainmaputil/program_command.h b/apps/avifgainmaputil/program_command.h
index 5c18b0bfe7..c60fb96f20 100644
--- a/apps/avifgainmaputil/program_command.h
+++ b/apps/avifgainmaputil/program_command.h
@@ -134,6 +134,8 @@ struct ImageReadArgs {
   argparse::ArgValue<int> pixel_format;
   argparse::ArgValue<bool> ignore_profile;
   argparse::ArgValue<bool> ignore_alpha;
+  argparse::ArgValue<bool> ignore_exif;
+  argparse::ArgValue<bool> ignore_xmp;
 
   void Init(argparse::ArgumentParser& argparse) {
     argparse
@@ -154,6 +156,18 @@ struct ImageReadArgs {
             "(no-op if absent)")
         .action(argparse::Action::STORE_TRUE)
         .default_value("false");
+    argparse.add_argument(ignore_exif, "--ignore-exif")
+        .help(
+            "If the input file contains embedded Exif metadata, ignore it "
+            "(no-op if absent)")
+        .action(argparse::Action::STORE_TRUE)
+        .default_value("false");
+    argparse.add_argument(ignore_xmp, "--ignore-xmp")
+        .help(
+            "If the input file contains embedded XMP metadata, ignore it "
+            "(no-op if absent)")
+        .action(argparse::Action::STORE_TRUE)
+        .default_value("false");
   }
 };
 
diff --git a/apps/avifgainmaputil/swapbase_command.cc b/apps/avifgainmaputil/swapbase_command.cc
index 14432a9d38..96aef5db71 100644
--- a/apps/avifgainmaputil/swapbase_command.cc
+++ b/apps/avifgainmaputil/swapbase_command.cc
@@ -166,6 +166,8 @@ avifResult SwapBaseCommand::Run() {
   decoder->maxThreads = arg_jobs_.jobs.value();
   decoder->imageContentToDecode |= AVIF_IMAGE_CONTENT_GAIN_MAP;
   decoder->ignoreICC = arg_image_read_.ignore_profile;
+  decoder->ignoreExif = arg_image_read_.ignore_exif;
+  decoder->ignoreXMP = arg_image_read_.ignore_xmp;
   avifResult result = ReadAvif(decoder.get(), arg_input_filename_);
   if (result != AVIF_RESULT_OK) {
     return result;
diff --git a/apps/avifgainmaputil/tonemap_command.cc b/apps/avifgainmaputil/tonemap_command.cc
index fa40592109..1e3b8fecfc 100644
--- a/apps/avifgainmaputil/tonemap_command.cc
+++ b/apps/avifgainmaputil/tonemap_command.cc
@@ -68,6 +68,8 @@ avifResult TonemapCommand::Run() {
   decoder->maxThreads = arg_jobs_.jobs.value();
   decoder->imageContentToDecode |= AVIF_IMAGE_CONTENT_GAIN_MAP;
   decoder->ignoreICC = arg_image_read_.ignore_profile;
+  decoder->ignoreExif = arg_image_read_.ignore_exif;
+  decoder->ignoreXMP = arg_image_read_.ignore_xmp;
   avifResult result = ReadAvif(decoder.get(), arg_input_filename_);
   if (result != AVIF_RESULT_OK) {
     return result;


Test patch:
diff --git a/tests/test_cmd_avifgainmaputil.sh b/tests/test_cmd_avifgainmaputil.sh
index b25061da56..a5da841220 100755
--- a/tests/test_cmd_avifgainmaputil.sh
+++ b/tests/test_cmd_avifgainmaputil.sh
@@ -59,11 +59,25 @@ pushd ${TMP_DIR}
   "${AVIFGAINMAPUTIL}" tonemap "${INPUT_AVIF_GAINMAP_SDR}" "${PNG_OUTPUT}" --headroom 0 --clli 400,500
   "${ARE_IMAGES_EQUAL}" "${PNG_OUTPUT}" "${INPUT_JPEG_GAINMAP_SDR}" 0 40 1
   # Check that metadata is copied over.
-  "${AVIFGAINMAPUTIL}" tonemap "${AVIF_OUTPUT}" "${AVIF_OUTPUT}" --headroom 0 > "${OUT_MSG}"
+  "${AVIFGAINMAPUTIL}" tonemap "${AVIF_OUTPUT}" "${AVIF_OUTPUT}.tonemapped.avif" --headroom 0 > "${OUT_MSG}"
   cat "${OUT_MSG}"
   grep "XMP Metadata   : Present" "${OUT_MSG}"
   grep "Exif Metadata  : Present" "${OUT_MSG}"
 
+  # Check that metadata can be ignored (tonemap command).
+  "${AVIFGAINMAPUTIL}" tonemap "${AVIF_OUTPUT}" "${AVIF_OUTPUT}.ignored.avif" --headroom 0 --ignore-xmp --ignore-exif > "${OUT_MSG}"
+  cat "${OUT_MSG}"
+  grep "XMP Metadata   : Absent" "${OUT_MSG}"
+  grep "Exif Metadata  : Absent" "${OUT_MSG}"
+
+  # Check that metadata can be ignored (combine command).
+  "${AVIFGAINMAPUTIL}" combine "${INPUT_AVIF_GAINMAP_HDR}" "${INPUT_AVIF_GAINMAP_SDR}" "${AVIF_OUTPUT}.ignored-combine.avif" \
+      -q 90 --qgain-map 90 --ignore-xmp --ignore-exif
+  "${AVIFGAINMAPUTIL}" tonemap "${AVIF_OUTPUT}.ignored-combine.avif" "${AVIF_OUTPUT}.ignored-combine.tonemapped.avif" --headroom 0 > "${OUT_MSG}"
+  cat "${OUT_MSG}"
+  grep "XMP Metadata   : Absent" "${OUT_MSG}"
+  grep "Exif Metadata  : Absent" "${OUT_MSG}"
+
   # Test combine with overridden cicp values. Matrix coefficient 0 (identity) makes it obvious if there is an issue.
   "${AVIFGAINMAPUTIL}" combine "${INPUT_JPEG_GAINMAP_SDR}" "${INPUT_AVIF_HDR2020}" "${AVIF_OUTPUT}" \
       -q 100 --qgain-map 100 --cicp-base 1/13/0 --ignore-profile