Skip to content

Conversation

@ThanhNguyxn
Copy link

Summary of the Pull Request

This PR enables PowerRename to extract EXIF metadata from HEIC/HEIF images (Apple's photo format) for use in file renaming patterns.

References and Relevant Issues

Fixes #43758

Detailed Description of the Pull Request / Additional comments

Root Cause Analysis

HEIC/HEIF files store EXIF metadata under a different path structure than JPEG/TIFF files:

  • JPEG/TIFF: /app1/ifd/exif/{ushort=...} and /app1/ifd/gps/{ushort=...}
  • HEIC/HEIF: /ifd/exif/{ushort=...} and /ifd/gps/{ushort=...}

The existing implementation only queried the standard JPEG/TIFF paths, so HEIC metadata was not found.

Changes Made

  1. Helpers.cpp: Added .heic and .heif to the supportedExtensions set in isMetadataUsed() function
  2. WICMetadataExtractor.cpp:
    • Added 23 HEIC-specific metadata query paths (18 EXIF + 5 GPS)
    • Added 4 fallback methods: ReadDateTimeWithFallback, ReadStringWithFallback, ReadIntegerWithFallback, ReadDoubleWithFallback
    • Updated ExtractAllEXIFFields() to use fallback methods (try standard path first, then HEIC path)
    • Updated ExtractGPSData() to use fallback paths for GPS data
  3. WICMetadataExtractor.h: Added declarations for the 4 fallback methods

Testing

  • Tested with HEIC images from iPhone containing EXIF data
  • Verified that standard JPEG/TIFF metadata extraction still works correctly

Validation Steps Performed

  • Built and verified no compilation errors
  • Tested metadata extraction from HEIC files
  • Tested that JPEG/TIFF extraction still works (no regression)

Copilot AI review requested due to automatic review settings December 3, 2025 03:05
@ThanhNguyxn
Copy link
Author

@microsoft-github-policy-service agree

Copilot finished reviewing on behalf of ThanhNguyxn December 3, 2025 03:08
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds support for extracting EXIF metadata from HEIC/HEIF image files (Apple's modern photo format) to PowerRename. HEIC files store EXIF data under different WIC metadata paths (/ifd/...) compared to JPEG/TIFF files (/app1/ifd/...), which prevented metadata extraction in the original implementation.

Key Changes

  • Added .heic and .heif extensions to the supported file types for metadata extraction
  • Implemented fallback mechanism that tries standard JPEG/TIFF paths first, then HEIC-specific paths
  • Added 23 HEIC-specific metadata path constants for EXIF and GPS data

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/modules/powerrename/lib/Helpers.cpp Added .heic and .heif to the list of supported image extensions for metadata extraction
src/modules/powerrename/lib/WICMetadataExtractor.h Added declarations for four fallback helper methods (ReadDateTimeWithFallback, ReadStringWithFallback, ReadIntegerWithFallback, ReadDoubleWithFallback)
src/modules/powerrename/lib/WICMetadataExtractor.cpp Added 23 HEIC-specific metadata path constants, implemented four fallback helper methods, and updated ExtractAllEXIFFields and ExtractGPSData to use fallback paths for HEIC support

Comment on lines 49 to 75
// HEIC/HEIF-specific EXIF metadata paths
// HEIC stores EXIF data directly under /ifd instead of /app1/ifd
const std::wstring HEIC_EXIF_DATE_TAKEN = L"/ifd/exif/{ushort=36867}"; // DateTimeOriginal
const std::wstring HEIC_EXIF_DATE_DIGITIZED = L"/ifd/exif/{ushort=36868}"; // DateTimeDigitized
const std::wstring HEIC_EXIF_DATE_MODIFIED = L"/ifd/{ushort=306}"; // DateTime
const std::wstring HEIC_EXIF_CAMERA_MAKE = L"/ifd/{ushort=271}"; // Make
const std::wstring HEIC_EXIF_CAMERA_MODEL = L"/ifd/{ushort=272}"; // Model
const std::wstring HEIC_EXIF_LENS_MODEL = L"/ifd/exif/{ushort=42036}"; // LensModel
const std::wstring HEIC_EXIF_ISO = L"/ifd/exif/{ushort=34855}"; // ISOSpeedRatings
const std::wstring HEIC_EXIF_APERTURE = L"/ifd/exif/{ushort=33437}"; // FNumber
const std::wstring HEIC_EXIF_SHUTTER_SPEED = L"/ifd/exif/{ushort=33434}"; // ExposureTime
const std::wstring HEIC_EXIF_FOCAL_LENGTH = L"/ifd/exif/{ushort=37386}"; // FocalLength
const std::wstring HEIC_EXIF_EXPOSURE_BIAS = L"/ifd/exif/{ushort=37380}"; // ExposureBiasValue
const std::wstring HEIC_EXIF_FLASH = L"/ifd/exif/{ushort=37385}"; // Flash
const std::wstring HEIC_EXIF_ORIENTATION = L"/ifd/{ushort=274}"; // Orientation
const std::wstring HEIC_EXIF_COLOR_SPACE = L"/ifd/exif/{ushort=40961}"; // ColorSpace
const std::wstring HEIC_EXIF_WIDTH = L"/ifd/exif/{ushort=40962}"; // PixelXDimension
const std::wstring HEIC_EXIF_HEIGHT = L"/ifd/exif/{ushort=40963}"; // PixelYDimension
const std::wstring HEIC_EXIF_ARTIST = L"/ifd/{ushort=315}"; // Artist
const std::wstring HEIC_EXIF_COPYRIGHT = L"/ifd/{ushort=33432}"; // Copyright

// HEIC GPS paths
const std::wstring HEIC_GPS_LATITUDE = L"/ifd/gps/{ushort=2}"; // GPSLatitude
const std::wstring HEIC_GPS_LATITUDE_REF = L"/ifd/gps/{ushort=1}"; // GPSLatitudeRef
const std::wstring HEIC_GPS_LONGITUDE = L"/ifd/gps/{ushort=4}"; // GPSLongitude
const std::wstring HEIC_GPS_LONGITUDE_REF = L"/ifd/gps/{ushort=3}"; // GPSLongitudeRef
const std::wstring HEIC_GPS_ALTITUDE = L"/ifd/gps/{ushort=6}"; // GPSAltitude
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test coverage for HEIC/HEIF metadata extraction. The existing WICMetadataExtractorTests.cpp has comprehensive tests for JPEG files, but no tests for the new HEIC/HEIF support. Consider adding test cases with sample HEIC files to verify:

  1. EXIF metadata extraction from HEIC files
  2. GPS data extraction from HEIC files
  3. Fallback behavior works correctly when HEIC paths are used

This is especially important given that the HEIC metadata paths differ from JPEG/TIFF and could have edge cases.

Copilot uses AI. Check for mistakes.
Comment on lines 564 to 615
auto lat = ReadMetadata(reader, GPS_LATITUDE);
if (!lat)
{
lat = ReadMetadata(reader, HEIC_GPS_LATITUDE);
}

auto lon = ReadMetadata(reader, GPS_LONGITUDE);
if (!lon)
{
lon = ReadMetadata(reader, HEIC_GPS_LONGITUDE);
}

auto latRef = ReadMetadata(reader, GPS_LATITUDE_REF);
if (!latRef)
{
latRef = ReadMetadata(reader, HEIC_GPS_LATITUDE_REF);
}

auto lonRef = ReadMetadata(reader, GPS_LONGITUDE_REF);
if (!lonRef)
{
lonRef = ReadMetadata(reader, HEIC_GPS_LONGITUDE_REF);
}
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The ExtractGPSData method uses a repetitive if-check pattern for each GPS field fallback. Consider refactoring this to use helper methods similar to ReadDoubleWithFallback for consistency and reduced code duplication. For example, you could create a ReadMetadataWithFallback helper that takes primary and fallback paths.

Copilot uses AI. Check for mistakes.
@ThanhNguyxn ThanhNguyxn force-pushed the fix/heic-exif-metadata branch from caa0d00 to 8550b9b Compare December 3, 2025 12:52
Copy link
Collaborator

@daverayment daverayment left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi.

I have a few concerns with this approach. Most importantly, there are no unit tests here. Please add some specific unit tests to cover each of the formats you aim to support. (There are existing tests for the JPEG format in the WICMetadataExtractorTests.cpp file. It may be possible to parameterise these, but that would need investigating.)

Secondly, while your code may work for the specific HEIC test case, it relies on the duplication of every metadata path and hard-coding specific fallback logic. This violates DRY and isn't scalable, creating a maintenance burden. For example, if we wanted to add support for PNG or WebP in the future, we'd have to declare more constants and more fallbacks.

A more flexible solution would be to separate the EXIF tag IDs (the {ushort=nnn} strings) from the paths. The tags are identical across the file formats, and the only difference is that the JPEG path differs from the TIFF/HEIC one, e.g. /app1/ifd/exif/ versus /ifd/exif.

I'd recommend restarting with a more extensible approach. For example:

  • Remove the duplicate constants
  • Declare each of the EXIF tags once
  • Create a vector of the valid root paths
  • Refactor the reading of metadata to search through the root paths combined with the tag IDs until a match is found
  • etc.

This will reduce the repetition, the need for fallback logic and allow us to extend the approach to other formats in the future.

@ThanhNguyxn ThanhNguyxn force-pushed the fix/heic-exif-metadata branch from 8550b9b to 2af741f Compare December 4, 2025 00:55
@ThanhNguyxn
Copy link
Author

@daverayment Thank you for the detailed feedback! I've completely refactored the implementation based on your suggestions.

Changes Made

1. Extensible Path System (DRY Principle)

  • Separated EXIF tag IDs from root paths: Each tag ID (e.g., {ushort=36867} for DateTimeOriginal) is now declared once
  • Created root path vectors: IFD_ROOT_PATHS, EXIF_ROOT_PATHS, GPS_ROOT_PATHS that can be easily extended
  • Added BuildMetadataPaths() helper: Combines root paths with tag IDs to build full query paths
// Root paths for EXIF sub-IFD metadata
const std::vector<std::wstring> EXIF_ROOT_PATHS = {
    L"/app1/ifd/exif",  // JPEG (APP1 marker)
    L"/ifd/exif"        // HEIC/HEIF/TIFF
};

// Tag IDs (identical across formats)
const std::wstring TAG_DATE_TAKEN = L"{ushort=36867}";

2. Multi-Path Reading Methods

  • Added ReadDateTimeFromPaths(), ReadStringFromPaths(), ReadIntegerFromPaths(), ReadDoubleFromPaths(), ReadMetadataFromPaths()
  • These methods iterate through all paths until metadata is found
  • Removed the repetitive WithFallback methods

3. Future Extensibility

To add support for new formats (PNG, WebP, etc.), you simply:

  1. Add new root paths to the vectors
  2. No changes needed to extraction logic

4. Unit Tests for HEIC/HEIF

  • Added ExtractHEICMetadataTests test class
  • Tests gracefully skip when HEIC test files aren't available
  • Tests handle missing HEIF Image Extensions gracefully

Note: I didn't include HEIC test files because:

  1. Need to find/create properly licensed test images
  2. HEIC testing requires Microsoft's HEIF Image Extensions to be installed

Would you like me to:

  1. Create a simple HEIC test image with known metadata?
  2. Add any additional test cases?

Thank you again for the guidance on making this more maintainable!

This commit enables PowerRename to extract EXIF metadata from HEIC/HEIF images
(Apple's photo format) for use in file renaming patterns.

Changes:
- Add .heic and .heif to supported metadata extensions in Helpers.cpp
- Add HEIC-specific metadata query paths (HEIC stores EXIF under /ifd instead of /app1/ifd)
- Add fallback methods to try standard paths first, then HEIC paths
- Update ExtractAllEXIFFields and ExtractGPSData to use fallback methods

Fixes microsoft#43758
- Separate EXIF tag IDs from root paths for better maintainability
- Create IFD_ROOT_PATHS, EXIF_ROOT_PATHS, GPS_ROOT_PATHS vectors
- Add ReadDateTimeFromPaths, ReadStringFromPaths, ReadIntegerFromPaths,
  ReadDoubleFromPaths, ReadMetadataFromPaths helper methods
- Refactor ExtractAllEXIFFields and ExtractGPSData to use path vectors
- Add HEIC/HEIF unit tests with graceful skip when test files unavailable

This refactoring follows DRY principles and makes it easy to add support
for additional image formats (PNG, WebP) in the future by simply adding
new root paths to the vectors.
@ThanhNguyxn ThanhNguyxn force-pushed the fix/heic-exif-metadata branch from c99c65e to c33a603 Compare December 5, 2025 11:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Can't use any of EXIF fields in PowerRename for HEIC files

2 participants