在C#中阅读JPEG、XMP或EXIF的数据元数据

20

我一直在寻找一种使用C#从JPEG文件中读取元数据(特别是拍摄日期)的好方法,但是目前进展不太顺利。根据我目前所见的信息,现有的代码类似于以下内容;

BitmapMetadata bmd = (BitmapMetadata)frame.Metadata;
string a1 = (string)bmd.GetQuery("/app1/ifd/exif:{uint=36867}");

但是由于我的无知,我不知道GetQuery()会返回什么元数据,也不知道该传递什么参数。

我想先尝试读取XMP,如果XMP不存在,则回退到EXIF。有简单的方法可以实现吗?

谢谢。


你对IPTC元数据不感兴趣吗?Jpeg文件可以包含三种不同类型的元数据,其中可能包括拍摄日期字段。 - hippietrail
6个回答

29

以下代码看起来很好,但如果有什么不好的地方,请提出任何建议。

    public string GetDate(FileInfo f)
    {
        using(FileStream fs = new FileStream(f.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
        {
            BitmapSource img = BitmapFrame.Create(fs);
            BitmapMetadata md = (BitmapMetadata)img.Metadata;
            string date = md.DateTaken;
            Console.WriteLine(date);
            return date;
        }
    }

@Lijo,我不知道BitmapMetadata是否提供GPS数据,但是如果你愿意的话,你可以很容易地使用我的库来实现。 - Drew Noakes
@tsvallender,你应该释放 FileStream 对象。 - Drew Noakes
你好,如何读取XMP全景标签,就像这里提到的一样:http://stackoverflow.com/questions/39066046/xmpgpano-meta-data-to-image-by-c-sharp-for-facebook-360-image - Andi AR
@tsvallender - 使用语句在块的末尾调用dispose方法。 - undefined

11

我最近将我的长期开源Java库移植到.NET,它支持各种图像格式中的XMP、Exif、ICC、JFIF和许多其他类型的元数据。它一定能够达成你的目标。

https://github.com/drewnoakes/metadata-extractor-dotnet

var directories = ImageMetadataReader.ReadMetadata(imagePath);
var subIfdDirectory = directories.OfType<ExifSubIfdDirectory>().FirstOrDefault();
var dateTime = subIfdDirectory?.GetDescription(ExifDirectoryBase.TagDateTime);

这个库还支持通过 Adobe 的 Java XmpCore 库的 C# 移植来处理 XMP 数据。

https://github.com/drewnoakes/xmp-core-dotnet


如果你需要原始捕获日期,最后一行应该是
string dateTime = subIfdDirectory?.GetDescription(ExifDirectoryBase.TagDateTimeOriginal);
或者你可以使用这个代码将它获取为 DateTime? 对象
DateTime? dateTime = subIfdDirectory?.GetDateTime(ExifDirectoryBase.TagDateTimeOriginal);
- modeeb
@drew-noakes,那个东西能处理Picasa区域/人脸数据吗? 我可以确认他的库确实很好地完成了你在这里请求的基本功能。 - twobob
1
@twobob,我已经有一段时间没有使用Picasa了,但我相信它将元数据存储在自己的数据库或旁路文件中。MetadatExtractor目前还没有支持旁路文件,但如果实现得好,我会接受一个pull request。 - Drew Noakes
嗨Drew。我使用了下面的brutalXmp变体,然后将其全部剥离了。(可以选择将数据存储在jpg中,这是在选项中的,以及将之前外部存储的数据写入文件中,也在选项中) 我把结果放在了那里供您审查(以及下一个不幸的灵魂,花费数天时间来解决如何做到这一点,没有库支持。是的,Unity3d)。 参考:https://gist.github.com/twobob/ea6cb3b7c7d83c1b62513bcd67c0d39c - twobob
实际上,如果有人想要走元数据提取器的路线,我现在意识到这个问题 https://dev59.com/CX_aa4cB1Zd3GeqP5JVe 也很有用。 - twobob

3
如果你在处理jpeg文件中的XMP有困难,这个方法很有效。它被称为“brutal”(残酷),并非没有原因!
public class BrutalXmp
{
    public XmlDocument ExtractXmp(byte[] jpegBytes)
    {
        var asString = Encoding.UTF8.GetString(jpegBytes);
        var start = asString.IndexOf("<x:xmpmeta");
        var end = asString.IndexOf("</x:xmpmeta>") + 12;
        if (start == -1 || end == -1)
            return null;
        var justTheMeta = asString.Substring(start, end - start);
        var returnVal = new XmlDocument();
        returnVal.LoadXml(justTheMeta);
        return returnVal;
    }
}

这非常适用于支持非常有限的情况。非常感谢您。只需应用GetElementsByTagName("rdf:Description")并小心处理,就可以提取Picassa3面部区域数据。 工作得很好。 - twobob
有时候我想知道为什么通常的框架不提供像这样简单的东西。有没有关于如何在不读取完整流的情况下完成类似事情的提示? - Daniel Möller
为了获取所有元数据(不仅仅是xmp),可以使用此选项:https://www.codeproject.com/Articles/66328/Enumerating-all-of-the-Metadata-Tags-in-an-Image-F - Daniel Möller

2
如果您正在尝试访问这些属性:

enter image description here

您可以执行以下操作:
  1. 添加对C:\Windows\System32\Shell32.dll的引用。VS 2022会自动创建一个交互式Interop以与ActiveX库交互。
  2. 我在按钮单击事件中添加了以下代码,以演示获取所需数据。

代码示例:

Shell32.Shell shell = new Shell32.Shell();
Shell32.Shell objShell = shell.Application;
Shell32.Folder folder = objShell.NameSpace(@"D:\TestFolder");
Shell32.FolderItem folderItem = folder.ParseName("TestMetadata.jpg");
for (int tagIndex = 0; tagIndex < 321; tagIndex++)
{
   // Pass null in the first parameter to get the tagName
   string tagName = folder.GetDetailsOf(null, tagIndex);

   if (!string.IsNullOrEmpty(tagName))
   {
      // Pass an instance of Shell32.FolderItem to get the tag value.
      string tagValue = folder.GetDetailsOf(folderItem, tagIndex);

      Console.WriteLine($"[{tagIndex}] {tagName} = {tagValue}");
   }
}

控制台将显示表示所关心值的字符串。我不确定为什么,但包含单个数字月份和/或日期值的日期将在0数字处显示一个问号。这些可以轻松地替换,然后可以解析日期。以下是在“属性\详细信息”选项卡中显示的一些有趣条目的输出:
[3] Date modified = 9/3/2022 2:37 PM
[4] Date created = 9/3/2022 2:35 PM
[5] Date accessed = 9/3/2022 10:38 PM
[12] Date taken = ?1/?1/?2022 ??2:36 PM
[18] Tags = Metadata Tags
[21] Title = My Test Title
[22] Subject = Being and Nothingness
[24] Comments = Kilroy wuz here!
[25] Copyright = 2022
[136] Date acquired = ?1/?2/?2022 ??2:36 PM

据我所知,有多达320种不同的标签类型。

1

我认为你所做的是一个好的解决方案,因为System.DateTaken处理程序会自动应用照片元数据策略以回退到其他命名空间查找值是否存在。


-4

我的公司生产了一个包含XMP和EXIF解析器的.NET 工具包

典型的流程大致如下:

XmpParser parser = new XmpParser();
System.Xml.XmlDocument xml = (System.Xml.XmlDocument)parser.ParseFromImage(stream, frameIndex);

对于 EXIF,您可以这样做:

ExitParser parser = new ExifParser();
ExifCollection exif = parser.ParseFromImage(stream, frameIndex);

显然,对于JPEG,frameIndex将为0。


12
谢谢,但恐怕这不是我能负担得起的项目花费。 - tsvallender
1
有免费和开源的替代方案。 - Drew Noakes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接