-
Notifications
You must be signed in to change notification settings - Fork 7.5k
Add HEIC/HEIF EXIF metadata support for PowerRename #44036
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
@microsoft-github-policy-service agree |
There was a problem hiding this 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
.heicand.heifextensions 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 |
| // 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 |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
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:
- EXIF metadata extraction from HEIC files
- GPS data extraction from HEIC files
- 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.
| 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); | ||
| } |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
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.
caa0d00 to
8550b9b
Compare
daverayment
left a comment
There was a problem hiding this 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.
8550b9b to
2af741f
Compare
|
@daverayment Thank you for the detailed feedback! I've completely refactored the implementation based on your suggestions. Changes Made1. Extensible Path System (DRY Principle)
// 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
3. Future ExtensibilityTo add support for new formats (PNG, WebP, etc.), you simply:
4. Unit Tests for HEIC/HEIF
Note: I didn't include HEIC test files because:
Would you like me to:
Thank you again for the guidance on making this more maintainable! |
2af741f to
c99c65e
Compare
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.
c99c65e to
c33a603
Compare
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:
/app1/ifd/exif/{ushort=...}and/app1/ifd/gps/{ushort=...}/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
.heicand.heifto thesupportedExtensionsset inisMetadataUsed()functionReadDateTimeWithFallback,ReadStringWithFallback,ReadIntegerWithFallback,ReadDoubleWithFallbackExtractAllEXIFFields()to use fallback methods (try standard path first, then HEIC path)ExtractGPSData()to use fallback paths for GPS dataTesting
Validation Steps Performed