18

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
  • 14,735
  • 16
  • 96
  • 147
tsvallender
  • 2,275
  • 4
  • 28
  • 40
  • 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

5 Answers5

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,298
  • 3
  • 59
  • 67
tsvallender
  • 2,275
  • 4
  • 28
  • 40
  • 6
    Whoever at Microsoft implemented `BitmapMetaData.DateTaken` is an PERFECT IDIOT! 1. Why is it `string` at all? Last line in `get` is `DateTime.ToString()` and first line in `set` is `Convert.ToDateTime()`. and 2.: `get` returns culture specific string and `set` expects culture insensitive string. IS THERE ANY QUALITY MANAGEMENT AT MICROSOFT AT ALL??? – springy76 Aug 29 '14 at 10:13
  • 17
    @springy76, actually you're being a bit unfair. In Exif data, dates are represented as strings. Some cameras use different formats to others, so there's no guarantee that MS's could ever write code to successfully parse any date string it encounters. At least it passes the raw string along to you so you can debug what's going on. – Drew Noakes Aug 12 '15 at 11:47
  • @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
  • @DrewNoakes no I'm not: Use Reflector/ILSpy/JustDecompile/DotPeek and look at `BitmapMetaData.DateTaken` yourself: WIC already provides a binary datetime value for this (a System.Runtime.InteropServices.ComTypes.FILETIME struct counting the number of 100-nanosecond intervals since January 1, 1601), but this property is f*cking it all up (please just reread what I already wrote). – springy76 Aug 13 '15 at 07:27
  • 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
9

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
  • 284,599
  • 158
  • 653
  • 723
  • 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
  • 24,062
  • 11
  • 66
  • 99
  • 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
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,550
  • 2
  • 27
  • 51
muruge
  • 4,047
  • 3
  • 37
  • 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
  • 47,047
  • 11
  • 77
  • 120