在C#中为PNG文件写入DateTimeOriginal标签的问题求助
问题背景
我用MetadataExtractor NuGet包提取PNG和JPG的元数据完全正常,但它不支持EXIF标签写入。调研后选用ExifLibNet,发现该包对JPG文件的读写功能完全正常,但对PNG文件无效——既读不出已存在的DateTimeOriginal等EXIF标签,写入后也无法保存生效。
代码复现(MAUI .NET 7项目,需安装MetadataExtractor 2.7.2和ExifLibNet 2.1.4)
引用命名空间:
using ExifLibrary; using MetadataExtractor; using MetadataExtractor.Formats.Exif;
按钮点击事件代码:
private void OnCounterClicked(object sender, EventArgs e) { //string aFile = @"C:\Users\Beb\Google Drive\Python\EXIF Cleanup\NEW\2019_10_05_173730_JCS.JPG"; string aFile = @"C:\Users\Beb\Google Drive\Python\EXIF Cleanup\NEW\IMG-20230420-WA0001_TAGGED 15 04 2023 13 00 ANDROID.png"; // ===================================================================== // USING METADATAEXTRACTOR 2.7.2 // PART I : 提取所有标签(正常工作) IEnumerable<MetadataExtractor.Directory> directories = ImageMetadataReader.ReadMetadata(aFile); foreach (var directory in directories) foreach (var tag in directory.Tags) Debug.WriteLine($"{directory.Name} - {tag.Name} = {tag.Description}"); // PART II : 读取DateTimeOriginal标签(正常工作) IEnumerable<Directory> dir = ImageMetadataReader.ReadMetadata(aFile); var subIfDir = dir.OfType<ExifSubIfdDirectory>().FirstOrDefault(); var dtOriginal = subIfDir?.GetDescription(ExifDirectoryBase.TagDateTimeOriginal); Debug.WriteLine($"Using MetadataExtractor, Date Time Original is : {dtOriginal.ToString()}"); // 测试文件输出:2023:04:15 13:00:00 // ===================================================================== // USING EXIFLIBNET 2.1.4 var img = ImageFile.FromFile(aFile); // PART I : 提取所有标签(JPG正常,PNG读不出2023:04:15的日期标签) foreach (ExifTag aTag in (ExifTag[])Enum.GetValues(typeof(ExifTag))) { ExifProperty exifTag = img.Properties.Get(aTag); if (exifTag != null) Debug.WriteLine($"{aTag.ToString()} ==> {exifTag.Value.ToString()}"); } // PART II : 读取特定日期标签(JPG正常,PNG全部返回null) ExifProperty tagDT = img.Properties.Get(ExifTag.DateTime); ExifProperty tagDTO = img.Properties.Get(ExifTag.DateTimeOriginal); ExifProperty tagDTD = img.Properties.Get(ExifTag.DateTimeDigitized); ExifProperty tagPNGCT = img.Properties.Get(ExifTag.PNGCreationTime); Debug.WriteLine($"DateTime ==> {(tagDT == null ? "null" : tagDT.Value.ToString())}"); Debug.WriteLine($"DateTime Original ==> {(tagDTO == null ? "null" : tagDTO.Value.ToString())}"); Debug.WriteLine($"DateTime Digitzed ==> {(tagDTD == null ? "null" : tagDTD.Value.ToString())}"); Debug.WriteLine($"DateTime PNG Creation Time ==> {(tagPNGCT == null ? "null" : tagPNGCT.Value.ToString())}"); // PART III : 尝试写入新日期标签(JPG正常,PNG无效果) DateTime newDate = new DateTime(2023, 04, 10, 13,37, 00); img.Properties.Set(ExifTag.DateTime, newDate); img.Properties.Set(ExifTag.DateTimeDigitized, newDate); img.Properties.Set(ExifTag.DateTimeOriginal, newDate); // ExifLibNet中该标签值为236867 img.Properties.Set(ExifTag.PNGCreationTime, newDate); img.Save(aFile); // PART IV : 重新读取验证(JPG显示新值,PNG仍无标签) img = ImageFile.FromFile(aFile); foreach (ExifTag aTag in (ExifTag[])Enum.GetValues(typeof(ExifTag))) { ExifProperty exifTag = img.Properties.Get(aTag); if (exifTag != null) Debug.WriteLine($"{aTag.ToString()} ==> {exifTag.Value.ToString()}"); } }
标签ID差异发现
根据标准定义,DateTimeOriginal标签的ID是0x9003(十进制36867)。对比两个库的源代码:
- MetadataExtractor中
TagDateTimeOriginal = 0x9003(直接使用标准标签ID) - ExifLibNet中
DateTimeOriginal = 236867(比标准值多了前缀"2")
问题原因解析
PNG与JPG的EXIF存储结构差异:
JPG的EXIF数据直接嵌入在APP1块中,遵循标准的Exif IFD结构,ExifLibNet能正确解析。而PNG的EXIF数据通常存储在iTXt块的"EXIF"字段中,不属于标准JPG的APP块结构,ExifLibNet的PNG解析逻辑可能未处理这种存储方式,导致无法读取或写入。ExifLibNet的标签枚举设计:
ExifLibNet的ExifTag枚举值并非单纯的标签ID,而是标签ID + IFD类型的位组合(比如高16位代表IFD类型,低16位是标签ID)。这种设计在JPG的标准IFD结构中能正确匹配,但PNG的EXIF数据不在预期的IFD路径中,导致标签匹配失败,读不出数据,写入时也无法正确关联到PNG的Exif块。ExifLibNet对PNG的支持不完善:
该库的核心功能偏向JPG的Exif处理,对PNG的Exif读写逻辑存在缺失——可能未实现PNG的Exif块读取,或写入时未将Exif数据正确存入PNG的iTXt/tEXt块,导致修改后的数据无法持久化。
解决方案建议
- 更换更完善的图像库:使用SixLabors.ImageSharp,它对PNG和JPG的Exif读写都有良好支持,API设计更直观,且持续维护。示例代码(写入DateTimeOriginal):
using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using (var image = Image.Load(aFile)) { var exifProfile = image.Metadata.ExifProfile ?? new ExifProfile(); exifProfile.SetValue(ExifTag.DateTimeOriginal, newDate); image.Metadata.ExifProfile = exifProfile; image.Save(aFile, new PngEncoder()); } - 手动处理PNG的Exif块:如果坚持使用现有库,可以手动解析PNG的
iTXt块,提取Exif数据进行修改后再写入,但实现复杂度较高。 - 提交Issue或修复ExifLibNet:如果有开发能力,可以查看ExifLibNet的PNG处理源码,补充Exif块的读写逻辑并提交PR。
内容的提问来源于stack exchange,提问作者Beb

