// // This application parses a photo and compares the output to the output of exiv2. // // It can be used to make test fixtures. Manual validation is always required. // // You need the exiv2 app for this to work. // using GLib; using System; using System.Text; using System.Collections.Generic; using System.Security.Cryptography; using TagLib; using TagLib.IFD; using TagLib.IFD.Tags; using TagLib.Xmp; public class GenerateTestFixtureApp { private static MD5 md5 = MD5.Create (); public static void Main (string [] args) { if(args.Length != 2) { Console.Error.WriteLine ("USAGE: mono GenerateTestFixture.exe NAME PATH"); return; } string name = args[0]; string path = args[1]; EmitHeader (name, path); GenerateIFDFixture (name, path); GenerateXMPFixture (name, path); EmitFooter (); } static Dictionary sub_ifds = new Dictionary (); static Dictionary sub_ifds_emitted = new Dictionary (); static void GenerateIFDFixture (string name, string path) { // First run exiv2 on it. string output, err; int code; var result = GLib.Process.SpawnCommandLineSync (String.Format ("./listData e {0}", path), out output, out err, out code); if (!result) { Console.Error.WriteLine ("Invoking listData failed, are you running from the examples folder?"); return; } Write ("// ---------- Start of IFD tests ----------"); foreach (string line in output.Split ('\n')) { string[] parts = line.Split (new char[] {'\t'}, 5); if (parts.Length == 0 || line.Trim ().Equals (String.Empty) || parts.Length != 5) continue; string tag_label = parts[0]; ushort tag = ushort.Parse (parts[1].Substring(2), System.Globalization.NumberStyles.HexNumber); string ifd = parts[2]; string type = parts[3]; uint length = uint.Parse (parts[4]); if (ifd == "NikonSi02xx" || ifd == "NikonVr" || ifd == "NikonPc" || ifd == "NikonWt" || ifd == "NikonIi" || ifd == "NikonLd3") { continue; // Exiv2 makes these up. } string val = ExtractKey (path, String.Format ("Exif.{0}.{1}", ifd, tag_label)); if (tag_label == "SubIFDs") { for (int i = 0; i < val.Split (' ').Length; i++) { var sub_ifd = String.Format ("SubImage{0}", sub_ifds.Count + 1); sub_ifds.Add (sub_ifd, sub_ifds.Count); } continue; } EnsureIFD (ifd); if (tag_label.Equals ("ExifTag")) type = "SubIFD"; if (tag_label.Equals ("MakerNote")) { type = "MakerNote"; val = String.Empty; // No need to echo. } if (tag_label.Equals ("InteroperabilityTag")) type = "SubIFD"; if (tag_label.Equals ("GPSTag")) type = "SubIFD"; if (tag_label.Equals ("JPEGInterchangeFormat")) type = "ThumbnailDataIFD"; if (tag_label.Equals ("Preview") && ifd.Equals ("Nikon3")) type = "SubIFD"; if (tag_label.Equals ("UserComment") && ifd.Equals ("Photo")) type = "UserComment"; if (tag_label.Equals ("StripOffsets")) type = "StripOffsets"; if (tag_label.Equals ("IPTCNAA")) type = "IPTCNAA"; if (tag_label.Equals ("XMLPacket")) type = "XMLPacket"; if (ifd.Equals ("MakerNote")) continue; // Exiv2 makes these up. Write ("// {1}.0x{0:X4} ({2}/{3}/{4}) \"{5}\"", tag, ifd, tag_label, type, length, length > 512 ? "(Value ommitted)" : val); if (ifd.Equals ("Image")) { EmitTestIFDEntryOpen ("structure", 0, tag, ifd); } else if (ifd.Equals ("Thumbnail")) { EmitTestIFDEntryOpen ("structure", 1, tag, ifd); } else if (ifd.Equals ("Image2")) { EmitTestIFDEntryOpen ("structure", 2, tag, ifd); } else if (ifd.Equals ("Image3")) { EmitTestIFDEntryOpen ("structure", 3, tag, ifd); } else if (ifd.Equals ("Photo")) { EmitTestIFDEntryOpen ("exif_structure", 0, tag, ifd); } else if (IsPartOfMakernote (ifd)) { EmitTestIFDEntryOpen ("makernote_structure", 0, tag, ifd); } else if (ifd.Equals ("NikonPreview")) { EmitTestIFDEntryOpen ("nikonpreview_structure", 0, tag, ifd); } else if (ifd.Equals ("Iop")) { EmitTestIFDEntryOpen ("iop_structure", 0, tag, ifd); } else if (ifd.Equals ("GPSInfo")) { EmitTestIFDEntryOpen ("gps_structure", 0, tag, ifd); } else if (ifd.Equals ("CanonCs")) { EmitTestIFDEntryOpen ("makernote_structure", 0, (ushort) CanonMakerNoteEntryTag.CameraSettings, ifd); } else if (ifd.Equals ("CanonSi")) { EmitTestIFDEntryOpen ("makernote_structure", 0, (ushort) CanonMakerNoteEntryTag.ShotInfo, ifd); } else if (ifd.Equals ("CanonCf")) { EmitTestIFDEntryOpen ("makernote_structure", 0, (ushort) CanonMakerNoteEntryTag.CustomFunctions, ifd); } else if (ifd.Equals ("CanonPi")) { EmitTestIFDEntryOpen ("makernote_structure", 0, (ushort) CanonMakerNoteEntryTag.PictureInfo, ifd); } else if (ifd.Equals ("CanonFi")) { EmitTestIFDEntryOpen ("makernote_structure", 0, (ushort) 0x93, ifd); } else if (ifd.Equals ("PanasonicRaw")) { EmitTestIFDEntryOpen ("pana_structure", 0, tag, ifd); } else if (sub_ifds.ContainsKey (ifd)) { EmitTestIFDEntryOpen (String.Format ("{0}_structure", ifd), 0, tag, ifd); } else { throw new Exception (String.Format ("Unknown IFD: {0}", ifd)); } if (ifd.Equals ("CanonCs") || ifd.Equals ("CanonSi") || ifd.Equals ("CanonCf") || ifd.Equals ("CanonPi")) { // This are a made-up directory by exiv2 EmitTestIFDIndexedShortEntry (tag, val); } else if (ifd.Equals ("CanonFi")) { // This are a made-up directory by exiv2 // And the fist both entries are combined to a long by exiv2. if (tag == 0x0001) { string val1 = ((ushort) UInt32.Parse (val)).ToString (); string val2 = ((ushort) (UInt32.Parse (val) >> 16)).ToString (); EmitTestIFDIndexedShortEntry (tag, val1); EmitTestIFDIndexedShortEntry (tag + 1, val2); } else { EmitTestIFDIndexedShortEntry (tag, val); } } else if (type.Equals ("Ascii")) { EmitTestIFDStringEntry (val); } else if (type.Equals ("Short") && length == 1) { EmitTestIFDShortEntry (val); } else if (type.Equals ("Short") && length > 1) { EmitTestIFDShortArrayEntry (val); } else if (type.Equals ("SShort") && length == 1) { EmitTestIFDSShortEntry (val); } else if (type.Equals ("SShort") && length > 1) { EmitTestIFDSShortArrayEntry (val); } else if (type.Equals ("Rational") && length == 1) { EmitTestIFDRationalEntry (val); } else if (type.Equals ("Rational") && length > 1) { EmitTestIFDRationalArrayEntry (val); } else if (type.Equals ("SRational") && length == 1) { EmitTestIFDSRationalEntry (val); } else if (type.Equals ("SRational") && length > 1) { EmitTestIFDSRationalArrayEntry (val); } else if (type.Equals ("Long") && length == 1) { EmitTestIFDLongEntry (val); } else if (type.Equals ("Long") && length > 1) { EmitTestIFDLongArrayEntry (val); } else if (type.Equals ("SLong") && length == 1) { EmitTestIFDSLongEntry (val); } else if (type.Equals ("Byte") && length == 1) { EmitTestIFDByteEntry (val); } else if (type.Equals ("Byte") && length > 1) { EmitTestIFDByteArrayEntry (val); } else if (type.Equals ("SByte") && length == 1) { EmitTestIFDSByteEntry (val); } else if (type.Equals ("SubIFD")) { EmitTestIFDSubIFDEntry (val); } else if (type.Equals ("ThumbnailDataIFD")) { EmitTestIFDThumbnailDataIFDEntry (val); } else if (type.Equals ("MakerNote")) { EmitTestIFDMakerNoteIFDEntry (val); } else if (type.Equals ("UserComment")) { EmitTestIFDUserCommentIFDEntry (val); } else if (type.Equals ("Undefined")) { EmitTestIFDUndefinedEntry (val); } else if (type.Equals ("StripOffsets")) { EmitTestIFDStripOffsetsEntry (val); } else if (type.Equals ("IPTCNAA")) { EmitTestIFDIPTCNAAEntry (val); } else if (type.Equals ("XMLPacket")) { EmitTestIFDXMLPacketEntry (val); } else { throw new Exception ("Unknown type: " + type); } EmitTestIFDEntryClose (); } Write (); Write ("// ---------- End of IFD tests ----------"); Write (); } static Dictionary xmp_prefixes = new Dictionary (); static void GenerateXMPFixture (string name, string path) { // First run exiv2 on it. string output, err; int code; var result = GLib.Process.SpawnCommandLineSync (String.Format ("./listData x {0}", path), out output, out err, out code); if (!result) { Console.Error.WriteLine ("Invoking exiv2 failed, do you have it installed?"); return; } if (output.Trim ().Equals ("")) return; Write (); Write ("// ---------- Start of XMP tests ----------"); Write (); Write ("XmpTag xmp = file.GetTag (TagTypes.XMP) as XmpTag;"); // Build prefix lookup dictionary. Type t = typeof(XmpTag); foreach (var member in t.GetMembers()) { if (!member.Name.EndsWith ("_NS")) continue; string val = (member as System.Reflection.FieldInfo).GetValue (null) as string; string prefix = XmpTag.NamespacePrefixes [val]; xmp_prefixes [prefix] = member.Name; } foreach (string line in output.Split ('\n')) { string[] parts = line.Split (new char[] {'\t'}, 3); if (parts.Length == 0 || line.Trim ().Equals (String.Empty)) continue; string label = parts[0]; string type = parts[1]; uint length = uint.Parse (parts[2]); string val = ExtractKey (path, label).Trim (); EmitXmpTest (label, type, length, val); } Write (); Write ("// ---------- End of XMP tests ----------"); Write (); } static void EmitXmpTest (string label, string type, uint length, string val) { if (label.Equals ("Xmp.xmpMM.InstanceID")) return; // Continue this, exiv2 makes it up from the about attr if (label.Equals ("Xmp.tiff.Orientation")) return; // exiv2 destroys this value var node_path = label.Split ('/'); Write ("// {0} ({1}/{2}) \"{3}\"", label, type, length, val); Write ("{"); Write ("var node = xmp.NodeTree;"); // Navigate to the correct node. foreach (var node in node_path) { var parts = node.Split ('.'); var partscolon = node.Split (':'); if (parts.Length == 3) { // Plain node int index = 0; string name = parts[2]; if (parts[2].EndsWith("]")) { int index_start = parts[2].LastIndexOf ("["); string index_str = parts[2].Substring (index_start+1, parts[2].Length-index_start-2); index = int.Parse (index_str); name = parts[2].Substring (0, index_start); } string ns = GetXmpNs (parts[1]); Write ("node = node.GetChild ({0}, \"{1}\");", ns, name); Write ("Assert.IsNotNull (node);"); if (index > 0) { Write ("node = node.Children [{0}];", index - 1); Write ("Assert.IsNotNull (node);"); } } else if (partscolon.Length == 2) { string ns = GetXmpNs (partscolon[0]); string name = partscolon[1]; Write ("node = node.GetChild ({0}, \"{1}\");", ns, name); Write ("Assert.IsNotNull (node);"); } else { throw new Exception ("Can't navigate to "+node); } } if (length > 0 && type.Equals ("XmpText")) { Write ("Assert.AreEqual (\"{0}\", node.Value);", val); Write ("Assert.AreEqual (XmpNodeType.Simple, node.Type);"); Write ("Assert.AreEqual (0, node.Children.Count);"); } else if (type.Equals ("XmpBag") && length == 1) { Write ("Assert.AreEqual (XmpNodeType.Bag, node.Type);"); Write ("Assert.AreEqual (\"\", node.Value);"); Write ("Assert.AreEqual ({0}, node.Children.Count);", length); Write ("Assert.AreEqual (\"{0}\", node.Children [0].Value);", val); } else if (type.Equals ("LangAlt") && length == 1) { var langparts = val.Split (new char [] {' '}, 2); string lang = langparts[0].Substring (langparts[0].IndexOf ('"')+1, langparts [0].Length - langparts[0].IndexOf ('"')-2); Write ("Assert.AreEqual (\"{0}\", node.Children [0].GetQualifier (XmpTag.XML_NS, \"lang\").Value);", lang); Write ("Assert.AreEqual (\"{0}\", node.Children [0].Value);", langparts[1]); } else if (type.Equals ("XmpSeq") && length == 1) { Write ("Assert.AreEqual (XmpNodeType.Seq, node.Type);"); Write ("Assert.AreEqual (\"\", node.Value);"); Write ("Assert.AreEqual ({0}, node.Children.Count);", length); Write ("Assert.AreEqual (\"{0}\", node.Children [0].Value);", val); } else if (type.Equals ("XmpSeq") && length > 1) { string [] vals = val.Split (','); Write ("Assert.AreEqual (XmpNodeType.Seq, node.Type);"); Write ("Assert.AreEqual (\"\", node.Value);"); Write ("Assert.AreEqual ({0}, node.Children.Count);", length); var per_iter = vals.Length / length; for (int i = 0; i < length; i++) { var builder = new List (); for (int j = 0; j < per_iter; j++) { builder.Add (vals[per_iter*i + j].Trim ()); } Write ("Assert.AreEqual (\"{0}\", node.Children [{1}].Value);", String.Join (", ", builder.ToArray ()), i); } } else if (type.Equals ("XmpBag") && length > 1) { string [] vals = val.Split (','); Write ("Assert.AreEqual (XmpNodeType.Bag, node.Type);"); Write ("Assert.AreEqual (\"\", node.Value);"); Write ("Assert.AreEqual ({0}, node.Children.Count);", length); Write ("var children_array = new System.Collections.Generic.List ();"); Write ("foreach (var child in node.Children)"); Write ("{"); Write ("children_array.Add (child.Value);"); Write ("}"); var per_iter = vals.Length / length; for (int i = 0; i < length; i++) { var builder = new List (); for (int j = 0; j < per_iter; j++) { builder.Add (vals[per_iter*i + j].Trim ()); } Write ("Assert.IsTrue (children_array.Contains (\"{0}\"));", String.Join (", ", builder.ToArray ())); } } else if (type.Equals ("XmpText") && length == 0 && val.StartsWith ("type=")) { if (val.Equals ("type=\"Bag\"")) { Write ("Assert.AreEqual (XmpNodeType.Bag, node.Type);"); } else if (val.Equals ("type=\"Struct\"")) { // We disagree with exiv2 on the meaning of Struct. In Taglib#, // struct is meant to denote parseType=Resource types only, not // the shorthand equivalent. Also see XmpNode.RenderInto() //Write ("Assert.AreEqual (XmpNodeType.Struct, node.Type);"); } else { throw new Exception ("Unknown type"); } } else { throw new Exception (String.Format ("Can't test this (type: {0}, length: {1})", type, length)); } Write ("}"); } static string ExtractKey (string file, string key) { string output, err; int code; var result = GLib.Process.SpawnCommandLineSync (String.Format ("./extractKey {0} {1}", file, key), out output, out err, out code); if (!result) { Console.Error.WriteLine ("Invoking extractKey failed, are you running from the examples folder?"); return String.Empty; } return output; } static string GetXmpNs (string prefix) { string result; if (prefix.Equals ("xmpBJ")) prefix = "xapBJ"; if (prefix.Equals ("xmpMM")) prefix = "xapMM"; if (prefix.Equals ("xmpRights")) prefix = "xapRights"; if (prefix.Equals ("MicrosoftPhoto_1_")) // We correct this invalid namespace internally prefix = "MicrosoftPhoto"; if (xmp_prefixes.TryGetValue (prefix, out result)) return String.Format ("XmpTag.{0}", result); throw new Exception ("Unknown namespace prefix: "+prefix); } static bool IsPartOfMakernote (string ifd) { return ifd.Equals ("MakerNote") || ifd.Equals ("Canon") || ifd.Equals ("Sony") || ifd.Equals ("Nikon1") || ifd.Equals ("Nikon2") || ifd.Equals ("Nikon3") || ifd.Equals ("Panasonic") || ifd.Equals ("Olympus") || ifd.Equals ("Pentax"); } static void EmitHeader (string name, string path) { int start = path.LastIndexOf ('/'); string filename = path.Substring (start+1); Write ("// TODO: This file is automatically generated"); Write ("// TODO: Further manual verification is needed"); Write (); Write ("using System;"); Write ("using NUnit.Framework;"); Write ("using TagLib.IFD;"); Write ("using TagLib.IFD.Entries;"); Write ("using TagLib.IFD.Tags;"); Write ("using TagLib.Xmp;"); Write ("using TagLib.Tests.Images.Validators;"); Write (); Write ("namespace TagLib.Tests.Images"); Write ("{"); Write ("[TestFixture]"); Write ("public class {0}", name); Write ("{"); Write ("[Test]"); Write ("public void Test ()"); Write ("{"); Write ("ImageTest.Run (\"{0}\",", filename); level++; Write ("new {0}InvariantValidator (),", name); Write ("NoModificationValidator.Instance"); level--; Write (");"); Write ("}"); Write ("}"); Write (); Write ("public class {0}InvariantValidator : IMetadataInvariantValidator", name); Write ("{"); Write ("public void ValidateMetadataInvariants (Image.File file)"); Write ("{"); Write ("Assert.IsNotNull (file);"); } static void EmitFooter () { Write ("}"); // Method Write ("}"); // Class Write ("}"); // Namespace } static bool is_panasonic_raw = false; static bool structure_emitted = false; static bool exif_emitted = false; static bool makernote_emitted = false; static bool makernote_is_canon = false; static bool makernote_is_nikon1 = false; static bool makernote_is_nikon2 = false; static bool makernote_is_nikon3 = false; static bool makernote_is_panasonic = false; static bool nikonpreview_emitted = false; static bool iop_emitted = false; static bool gps_emitted = false; static void EnsureIFD (string ifd) { if (ifd.Equals ("PanasonicRaw")) { if (is_panasonic_raw) return; Write (); Write ("var tag = file.GetTag (TagTypes.TiffIFD) as IFDTag;"); Write ("Assert.IsNotNull (tag, \"IFD tag not found\");"); Write (); Write ("var pana_structure = tag.Structure;"); Write (); Write ("var jpg_file = (file as TagLib.Tiff.Rw2.File).JpgFromRaw;"); Write ("Assert.IsNotNull (tag, \"JpgFromRaw not found!\");"); Write ("var jpg_tag = jpg_file.GetTag (TagTypes.TiffIFD) as IFDTag;"); Write ("Assert.IsNotNull (tag, \"Jpg has no Exif tag!\");"); Write ("var structure = jpg_tag.Structure;"); is_panasonic_raw = true; } if (ifd.Equals ("Image") && !is_panasonic_raw) { if (structure_emitted) return; Write (); Write ("var tag = file.GetTag (TagTypes.TiffIFD) as IFDTag;"); Write ("Assert.IsNotNull (tag, \"IFD tag not found\");"); Write (); Write ("var structure = tag.Structure;"); Write (); structure_emitted = true; } if (ifd.Equals ("Photo")) { if (exif_emitted) return; EnsureIFD ("Image"); Write (); Write ("var exif = structure.GetEntry (0, (ushort) IFDEntryTag.ExifIFD) as SubIFDEntry;"); Write ("Assert.IsNotNull (exif, \"Exif tag not found\");"); Write ("var exif_structure = exif.Structure;"); Write (); exif_emitted = true; } if (ifd.Equals ("MakerNote")) { if (makernote_emitted) return; EnsureIFD ("Photo"); Write (); Write ("var makernote = exif_structure.GetEntry (0, (ushort) ExifEntryTag.MakerNote) as MakernoteIFDEntry;"); Write ("Assert.IsNotNull (makernote, \"MakerNote tag not found\");"); Write ("var makernote_structure = makernote.Structure;"); Write (); makernote_emitted = true; } if (ifd.Equals ("Canon") || ifd.Equals ("CanonCs") || ifd.Equals ("CanonSi")) { if (makernote_is_canon) return; EnsureIFD ("MakerNote"); Write (); Write ("Assert.AreEqual (MakernoteType.Canon, makernote.MakernoteType);"); Write (); makernote_is_canon = true; } if (ifd.Equals ("Nikon1")) { if (makernote_is_nikon1) return; EnsureIFD ("MakerNote"); Write (); Write ("Assert.AreEqual (MakernoteType.Nikon1, makernote.MakernoteType);"); Write (); makernote_is_nikon1 = true; } if (ifd.Equals ("Nikon2")) { if (makernote_is_nikon2) return; EnsureIFD ("MakerNote"); Write (); Write ("Assert.AreEqual (MakernoteType.Nikon2, makernote.MakernoteType);"); Write (); makernote_is_nikon2 = true; } if (ifd.Equals ("Nikon3")) { if (makernote_is_nikon3) return; EnsureIFD ("MakerNote"); Write (); Write ("Assert.AreEqual (MakernoteType.Nikon3, makernote.MakernoteType);"); Write (); makernote_is_nikon3 = true; } if (ifd.Equals ("NikonPreview")) { if (nikonpreview_emitted) return; EnsureIFD ("Nikon3"); Write (); Write ("var nikonpreview = makernote_structure.GetEntry (0, (ushort) Nikon3MakerNoteEntryTag.Preview) as SubIFDEntry;"); Write ("Assert.IsNotNull (nikonpreview, \"Nikon preview tag not found\");"); Write ("var nikonpreview_structure = nikonpreview.Structure;"); Write (); nikonpreview_emitted = true; } if (ifd.Equals ("Panasonic")) { if (makernote_is_panasonic) return; EnsureIFD ("MakerNote"); Write (); Write ("Assert.AreEqual (MakernoteType.Panasonic, makernote.MakernoteType);"); Write (); makernote_is_panasonic = true; } if (ifd.Equals ("Iop")) { if (iop_emitted) return; EnsureIFD ("Photo"); Write (); Write ("var iop = exif_structure.GetEntry (0, (ushort) IFDEntryTag.InteroperabilityIFD) as SubIFDEntry;"); Write ("Assert.IsNotNull (iop, \"Iop tag not found\");"); Write ("var iop_structure = iop.Structure;"); Write (); iop_emitted = true; } if (ifd.Equals ("GPSInfo")) { if (gps_emitted) return; EnsureIFD ("Image"); Write (); Write ("var gps = structure.GetEntry (0, (ushort) IFDEntryTag.GPSIFD) as SubIFDEntry;"); Write ("Assert.IsNotNull (gps, \"GPS tag not found\");"); Write ("var gps_structure = gps.Structure;"); Write (); gps_emitted = true; } if (sub_ifds.ContainsKey (ifd) && !sub_ifds_emitted.ContainsKey (ifd)) { Write (); Write ("var {0}_structure = (structure.GetEntry (0, (ushort) IFDEntryTag.SubIFDs) as SubIFDArrayEntry).Entries [{1}];", ifd, sub_ifds[ifd]); Write ("Assert.IsNotNull ({0}_structure, \"{0} structure not found\");", ifd); Write (); sub_ifds_emitted.Add (ifd, true); } } static void EmitTestIFDEntryOpen (string src, int ifd, ushort tag, string ifd_label) { Write ("{"); Write (String.Format ("var entry = {0}.GetEntry ({1}, (ushort) {2});", src, ifd, StringifyEntryTag (ifd_label, tag))); Write (String.Format ("Assert.IsNotNull (entry, \"Entry 0x{0:X4} missing in IFD {1}\");", tag, ifd)); } static void EmitTestIFDEntryClose () { Write ("}"); } static void EmitTestIFDStringEntry (string val) { Write ("Assert.IsNotNull (entry as StringIFDEntry, \"Entry is not a string!\");"); Write ("Assert.AreEqual (\"{0}\", (entry as StringIFDEntry).Value{1});", val, val == String.Empty ? ".Trim ()" : ""); } static void EmitTestIFDShortEntry (string val) { Write ("Assert.IsNotNull (entry as ShortIFDEntry, \"Entry is not a short!\");"); Write ("Assert.AreEqual ({0}, (entry as ShortIFDEntry).Value);", val); } static void EmitTestIFDSShortEntry (string val) { Write ("Assert.IsNotNull (entry as SShortIFDEntry, \"Entry is not a signed short!\");"); Write ("Assert.AreEqual ({0}, (entry as SShortIFDEntry).Value);", val); } static void EmitTestIFDShortArrayEntry (string val) { val = String.Format ("new ushort [] {{ {0} }}", String.Join (", ", val.Split(' '))); Write ("Assert.IsNotNull (entry as ShortArrayIFDEntry, \"Entry is not a short array!\");"); Write ("Assert.AreEqual ({0}, (entry as ShortArrayIFDEntry).Values);", val); } static void EmitTestIFDSShortArrayEntry (string val) { val = String.Format ("new short [] {{ {0} }}", String.Join (", ", val.Split(' '))); Write ("Assert.IsNotNull (entry as SShortArrayIFDEntry, \"Entry is not a signed short array!\");"); Write ("Assert.AreEqual ({0}, (entry as SShortArrayIFDEntry).Values);", val); } static void EmitTestIFDRationalEntry (string val) { Write ("Assert.IsNotNull (entry as RationalIFDEntry, \"Entry is not a rational!\");"); string[] parts = val.Split('/'); Write ("Assert.AreEqual ({0}, (entry as RationalIFDEntry).Value.Numerator);", parts [0]); Write ("Assert.AreEqual ({0}, (entry as RationalIFDEntry).Value.Denominator);", parts [1]); } static void EmitTestIFDRationalArrayEntry (string val) { var parts = val.Split(' '); Write ("Assert.IsNotNull (entry as RationalArrayIFDEntry, \"Entry is not a rational array!\");"); Write ("var parts = (entry as RationalArrayIFDEntry).Values;"); Write ("Assert.AreEqual ({0}, parts.Length);", parts.Length); for (int i = 0; i < parts.Length; i++) { var pieces = parts[i].Split('/'); Write ("Assert.AreEqual ({0}, parts[{1}].Numerator);", pieces[0], i); Write ("Assert.AreEqual ({0}, parts[{1}].Denominator);", pieces[1], i); } } static void EmitTestIFDSRationalEntry (string val) { Write ("Assert.IsNotNull (entry as SRationalIFDEntry, \"Entry is not a srational!\");"); string[] parts = val.Split('/'); Write ("Assert.AreEqual ({0}, (entry as SRationalIFDEntry).Value.Numerator);", parts [0]); Write ("Assert.AreEqual ({0}, (entry as SRationalIFDEntry).Value.Denominator);", parts [1]); } static void EmitTestIFDSRationalArrayEntry (string val) { var parts = val.Split(' '); Write ("Assert.IsNotNull (entry as SRationalArrayIFDEntry, \"Entry is not a srational array!\");"); Write ("var parts = (entry as SRationalArrayIFDEntry).Values;"); Write ("Assert.AreEqual ({0}, parts.Length);", parts.Length); for (int i = 0; i < parts.Length; i++) { var pieces = parts[i].Split('/'); Write ("Assert.AreEqual ({0}, parts[{1}].Numerator);", pieces[0], i); Write ("Assert.AreEqual ({0}, parts[{1}].Denominator);", pieces[1], i); } } static void EmitTestIFDLongEntry (string val) { Write ("Assert.IsNotNull (entry as LongIFDEntry, \"Entry is not a long!\");"); Write ("Assert.AreEqual ({0}, (entry as LongIFDEntry).Value);", val); } static void EmitTestIFDLongArrayEntry (string val) { val = String.Format ("new long [] {{ {0} }}", String.Join (", ", val.Split(' '))); Write ("Assert.IsNotNull (entry as LongArrayIFDEntry, \"Entry is not a long array!\");"); Write ("Assert.AreEqual ({0}, (entry as LongArrayIFDEntry).Values);", val); } static void EmitTestIFDSLongEntry (string val) { Write ("Assert.IsNotNull (entry as SLongIFDEntry, \"Entry is not a signed long!\");"); Write ("Assert.AreEqual ({0}, (entry as SLongIFDEntry).Value);", val); } static void EmitTestIFDByteEntry (string val) { Write ("Assert.IsNotNull (entry as ByteIFDEntry, \"Entry is not a byte!\");"); Write ("Assert.AreEqual ({0}, (entry as ByteIFDEntry).Value);", val); } static void EmitTestIFDByteArrayEntry (string val) { EmitByteArrayComparison (val, "ByteVectorIFDEntry", "a byte array"); } static void EmitTestIFDSByteEntry (string val) { Write ("Assert.IsNotNull (entry as SByteIFDEntry, \"Entry is not a signed byte!\");"); Write ("Assert.AreEqual ({0}, (entry as SByteIFDEntry).Value);", val); } static void EmitTestIFDIPTCNAAEntry (string val) { Write ("Assert.IsNotNull (entry as ByteVectorIFDEntry, \"Entry is not a byte array!\");"); } static void EmitTestIFDXMLPacketEntry (string val) { Write ("Assert.IsNotNull (entry as ByteVectorIFDEntry, \"Entry is not a byte array!\");"); } static void EmitTestIFDUndefinedEntry (string val) { EmitByteArrayComparison (val, "UndefinedIFDEntry", "an undefined IFD entry"); } static void EmitByteArrayComparison (string val, string type, string type_desc) { Write ("Assert.IsNotNull (entry as {0}, \"Entry is not {1}!\");", type, type_desc); Write ("var parsed_bytes = (entry as {0}).Data.Data;", type); var parts = val.Trim ().Split(' '); if (parts.Length < 512) { Write ("var bytes = new byte [] {{ {0} }};", String.Join (", ", parts)); Write ("Assert.AreEqual (bytes, parsed_bytes);"); } else { // Starting with 512 byte items, we compare based on an MD5 hash, should be faster and reduces // the size of the test fixtures. byte [] data = new byte [parts.Length]; for (int i = 0; i < parts.Length; i++) { data [i] = Byte.Parse (parts [i]); } var hash = md5.ComputeHash (data); StringBuilder shash = new StringBuilder (); for (int i = 0; i < hash.Length; i++) { shash.Append (hash[i].ToString ("x2")); } Write ("var parsed_hash = Utils.Md5Encode (parsed_bytes);"); Write ("Assert.AreEqual (\"{0}\", parsed_hash);", shash.ToString ()); Write ("Assert.AreEqual ({0}, parsed_bytes.Length);", parts.Length); } } static void EmitTestIFDSubIFDEntry (string val) { Write ("Assert.IsNotNull (entry as SubIFDEntry, \"Entry is not a sub IFD!\");"); } static void EmitTestIFDThumbnailDataIFDEntry (string val) { Write ("Assert.IsNotNull (entry as ThumbnailDataIFDEntry, \"Entry is not a thumbnail IFD!\");"); } static void EmitTestIFDMakerNoteIFDEntry (string val) { Write ("Assert.IsNotNull (entry as MakernoteIFDEntry, \"Entry is not a makernote IFD!\");"); } static void EmitTestIFDUserCommentIFDEntry (string val) { Write ("Assert.IsNotNull (entry as UserCommentIFDEntry, \"Entry is not a user comment!\");"); if (val.StartsWith ("charset=\"Ascii\"")) val = val.Substring (15).Trim (); Write ("Assert.AreEqual (\"{0}\", (entry as UserCommentIFDEntry).Value.Trim ());", val); } static void EmitTestIFDStripOffsetsEntry (string val) { // The offsets may change after writing. Therfore we cannot compare them directly. string offset_count = String.Format ("{0}", val.Split(' ').Length); //val = String.Format ("new long [] {{ {0} }}", String.Join (", ", val.Split(' '))); Write ("Assert.IsNotNull (entry as StripOffsetsIFDEntry, \"Entry is not a strip offsets entry!\");"); //Write ("Assert.AreEqual ({0}, (entry as StripOffsetsIFDEntry).Values);", val); Write ("Assert.AreEqual ({0}, (entry as StripOffsetsIFDEntry).Values.Length);", offset_count); } static void EmitTestIFDIndexedShortEntry (int index, string val) { Write ("Assert.IsNotNull (entry as ShortArrayIFDEntry, \"Entry is not a short array!\");"); var parts = val.Trim ().Split (' '); Write ("Assert.IsTrue ({0} <= (entry as ShortArrayIFDEntry).Values.Length);", index + parts.Length); for (int i = 0; i < parts.Length; i++) Write ("Assert.AreEqual ({0}, (entry as ShortArrayIFDEntry).Values [{1}]);", parts [i], index + i); } #region IFD tag names lookup static Dictionary> tag_names = null; static string StringifyEntryTag (string src, ushort tag) { if (tag_names == null) BuildTagNamesTable (); Dictionary table; string result; if (tag_names.TryGetValue (src, out table)) { if (table.TryGetValue (tag, out result)) return result; } Write ("// TODO: Unknown IFD tag: {1} / 0x{0:X4}", tag, src); return String.Format ("0x{0:X4}", tag); } static void BuildTagNamesTable () { tag_names = new Dictionary> (); IndexTagType ("Image", typeof (IFDEntryTag), "IFDEntryTag"); IndexTagType ("Image2", typeof (IFDEntryTag), "IFDEntryTag"); IndexTagType ("Image3", typeof (IFDEntryTag), "IFDEntryTag"); IndexTagType ("SubImage1", typeof (IFDEntryTag), "IFDEntryTag"); IndexTagType ("SubImage2", typeof (IFDEntryTag), "IFDEntryTag"); IndexTagType ("Thumbnail", typeof (IFDEntryTag), "IFDEntryTag"); // IFD1, for thumbnails IndexTagType ("Photo", typeof (IFDEntryTag), "IFDEntryTag"); IndexTagType ("Photo", typeof (ExifEntryTag), "ExifEntryTag"); IndexTagType ("Image", typeof (ExifEntryTag), "ExifEntryTag"); // Also put exif into Image, for DNG IndexTagType ("GPSInfo", typeof (GPSEntryTag), "GPSEntryTag"); IndexTagType ("Iop", typeof (IOPEntryTag), "IOPEntryTag"); IndexTagType ("Canon", typeof (CanonMakerNoteEntryTag), "CanonMakerNoteEntryTag"); IndexTagType ("CanonCs", typeof (CanonMakerNoteEntryTag), "CanonMakerNoteEntryTag"); IndexTagType ("CanonSi", typeof (CanonMakerNoteEntryTag), "CanonMakerNoteEntryTag"); IndexTagType ("CanonCf", typeof (CanonMakerNoteEntryTag), "CanonMakerNoteEntryTag"); IndexTagType ("CanonFi", typeof (CanonFileInfoEntryTag), "CanonFileInfoEntryTag"); IndexTagType ("CanonFi", typeof (CanonMakerNoteEntryTag), "CanonMakerNoteEntryTag"); IndexTagType ("CanonPi", typeof (CanonPictureInfoEntryTag), "CanonPictureInfoEntryTag"); IndexTagType ("CanonPi", typeof (CanonMakerNoteEntryTag), "CanonMakerNoteEntryTag"); IndexTagType ("Sony", typeof (SonyMakerNoteEntryTag), "SonyMakerNoteEntryTag"); IndexTagType ("Olympus", typeof (OlympusMakerNoteEntryTag), "OlympusMakerNoteEntryTag"); IndexTagType ("Pentax", typeof (PentaxMakerNoteEntryTag), "PentaxMakerNoteEntryTag"); IndexTagType ("Nikon3", typeof (Nikon3MakerNoteEntryTag), "Nikon3MakerNoteEntryTag"); IndexTagType ("NikonPreview", typeof (NikonPreviewMakerNoteEntryTag), "NikonPreviewMakerNoteEntryTag"); IndexTagType ("Panasonic", typeof (PanasonicMakerNoteEntryTag), "PanasonicMakerNoteEntryTag"); IndexTagType ("PanasonicRaw", typeof (IFDEntryTag), "IFDEntryTag"); } static void IndexTagType (string ifd, Type t, string typename) { if (!tag_names.ContainsKey (ifd)) tag_names[ifd] = new Dictionary (); foreach (string name in Enum.GetNames (t)) { ushort tag = (ushort) Enum.Parse (t, name); tag_names[ifd][tag] = String.Format ("{1}.{0}", name, typename); } } #endregion #region Code emission static int level = 0; static void Write (string str, params object[] p) { Console.Write (new String ('\t', level)); Console.WriteLine (str, p); } static void Write () { Console.WriteLine (); } static void Write (string str) { if (str.Equals ("}")) level--; Console.Write (new String ('\t', level)); Console.WriteLine (str); if (str.Equals ("{")) level++; } #endregion }