20

I've been looking around for a decent way of reading metadata (specifically, the date taken) from JPEG files in C#, and am coming up a little short. Existing information, as far as I can see, shows code like the following;

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

But in my ignorance I have no idea what bit of metadata GetQuery() will return, or what to pass it.

I want to attempt reading XMP first, falling back to EXIF if XMP does not exist. Is there a simple way of doing this?

Thanks.

hippietrail
  • 15,848
  • 18
  • 99
  • 158
tsvallender
  • 2,615
  • 6
  • 32
  • 42
  • Are you not also interested in IPTC metadata? Jpeg files can contain three distinct types of metadata which can include a date taken field. – hippietrail Jul 18 '19 at 12:21

6 Answers6

29

The following seems to work nicely, but if there's something bad about it, I'd appreciate any comments.

    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;
        }
    }
takrl
  • 6,356
  • 3
  • 60
  • 69
tsvallender
  • 2,615
  • 6
  • 32
  • 42
  • @Lijo, I don't know if `BitmapMetadata` provides GPS data, but you can easily use [my library](https://github.com/drewnoakes/metadata-extractor-dotnet) to do so if you like. – Drew Noakes Aug 12 '15 at 11:48
  • @tsvallender, you should dispose the `FileStream` object. – Drew Noakes Aug 12 '15 at 11:51
  • Hi how to read xmp pano tags like mentioned here http://stackoverflow.com/questions/39066046/xmpgpano-meta-data-to-image-by-c-sharp-for-facebook-360-image – Andi AR Aug 21 '16 at 16:34
11

I've ported my long-time open-source Java library to .NET recently, and it supports XMP, Exif, ICC, JFIF and many more types of metadata across a range of image formats. It will definitely achieve what you're after.

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

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

This library also supports XMP data, via a C# port of Adobe's XmpCore library for Java.

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

Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
  • If you need the original capture date, the last line should be `string dateTime = subIfdDirectory?.GetDescription(ExifDirectoryBase.TagDateTimeOriginal);` Or you can use this to get it in DateTime? object `DateTime? dateTime = subIfdDirectory?.GetDateTime(ExifDirectoryBase.TagDateTimeOriginal);` – modeeb Feb 20 '18 at 14:17
  • @drew-noakes can that thing handle Picassa Region/Face data drew? I can confirm that his library does indeed work nicely for the basics you are requesting here. – twobob Jul 11 '18 at 09:41
  • 1
    @twobob it's been a while since I used Picasa, but I believe it stores its metadata in its own database or in sidecar files. MetadatExtractor doesn't yet have any support for sidecar files, though I would accept a pull request if the implementation was decent. – Drew Noakes Jul 11 '18 at 13:43
  • Hi drew. I used a variation of the brutalXmp below and just ripped it out wholesale. (one can store the data inside the jpgs optionally, it's in options, and write previously externally stored data into the files - also in options) I shoved up the results for your perusal (and the next poor soul who spends days working it out how to do this, with no library support. Yup, Unity3d) REFERENCE: https://gist.github.com/twobob/ea6cb3b7c7d83c1b62513bcd67c0d39c – twobob Jul 11 '18 at 16:30
  • Actually , this question https://stackoverflow.com/questions/23595560/how-to-read-xmp-face-data-from-jpeg-in-java is also of use should one wish to go down the meta extractor route I now realise – twobob Jul 11 '18 at 22:46
3

If you're struggling with XMP jn jpeg, this works. It's not called brutal for nothing!

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;
    }
}
bbsimonbb
  • 27,056
  • 15
  • 80
  • 110
  • This is perfect for cases where support is very limiting. Many many thanks for this. with just the application of a `GetElementsByTagName("rdf:Description")` and some care one can extract Picassa3 face Region data with this. Top job. – twobob Jul 11 '18 at 16:32
  • Sometimes I wonder why in heavens don't the usual frameworks provide simple stuff like this. Any hints on doing similar things without reading the full stream? – Daniel Möller Oct 03 '19 at 13:33
  • In order to get all metadata (not only xmp), this option works: https://www.codeproject.com/Articles/66328/Enumerating-all-of-the-Metadata-Tags-in-an-Image-F – Daniel Möller Oct 03 '19 at 14:42
2

If you are trying to access these properties:

enter image description here

You can do the following:

  1. Add a reference to C:\Windows\System32\Shell32.dll. VS 2022 automatically creates an interop to interact with the ActiveX library.
  2. I added the following code to a button click event to demonstrate getting the data desired.

Code Sample:

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}");
   }
}

The console will display strings representing the values of interest. I'm not sure why, by dates containing single digit month and/or day values will display a ? for the 0 digit. These can easily be replaced and then the date can be parsed. Here is the output for some of the interesting ones displayed in the Properties\Details tab:

[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

As far as I am aware, there are as many as 320 different tag types.

Mitselplik
  • 1,079
  • 1
  • 12
  • 16
1

I think what you are doing is a good solution because the System.DateTaken handler automatically apply Photo metadata policies of falling back to other namespaces to find if a value exist.

Marcel Gosselin
  • 4,610
  • 2
  • 31
  • 54
muruge
  • 4,083
  • 3
  • 38
  • 45
-4

My company makes a .NET toolkit that includes XMP and EXIF parsers.

The typical process is something like this:

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

for EXIF you would do this:

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

obviously, frameIndex would be 0 for JPEG.

plinth
  • 48,267
  • 11
  • 78
  • 120