Sunday, February 12, 2006

One of the most popular search hits for my blog is "Managed XMP Parser".  A while back (actually it was 1 year ago today...whoa, freaky), I blogged about extracting the XMP data out of my pictures after screwing up the upload into Flickr.  I ended up writing my own code to pull out the XMP data.  I mentioned making it available, but it was relatively straightforward, so I never got around to posting it.

In the last week, I've gotten lots of requests for the code, so here it is, uglyness and all.  One interesting thing about my approach is that I do not rely on any particular file format.  I simply look for the XMP markers and pull out the XML in-between.  This means it will work on ANY file with embedded XMP.

All the usual disclaimers apply.  I don't claim this is the best way, but it works.  I've just plucked it out of my little date fixing app I built.  At the end, you'll have an XPathNavigator and a namespace manager setup to run XPath queries.  There's probably some sweet stuff the 2.0 can help us out with, but I haven't updated it.  Enjoy:

MemoryStream xmpStream = new MemoryStream();

byte[] beginPattern = Encoding.ASCII.GetBytes("<?xpacket begin");

int beginIndex=0;

bool beginFound = false;

byte[] beginStopPattern = Encoding.ASCII.GetBytes(">\n");

int beginStopIndex = 0;

bool xmlStartFound = false;

byte[] endPattern = Encoding.ASCII.GetBytes("<?xpacket end");

int endIndex=0;

bool endFound = false;

bool backedUp = false;

using (Stream stream = new FileStream(path, FileMode.Open)) {

      int data;

      while ((data = stream.ReadByte()) != -1) {

            byte b = (byte)data;

            if (!beginFound) {

                  if (b == beginPattern[beginIndex]) {

                        beginIndex++;

                        if (beginIndex >= beginPattern.Length) {

                              beginFound = true;

                        }

                  }

                  else {

                        if (beginIndex != 0) {

                              beginIndex = 0;

                              stream.Seek(-1, SeekOrigin.Current);

                        }

                  }

            }

            else if (!xmlStartFound) {

                  if (b == beginStopPattern[beginStopIndex]) {

                        beginStopIndex++;

                        if (beginStopIndex >= beginStopPattern.Length) {

                              xmlStartFound = true;

                        }

                  }

                  else {

                        if (beginStopIndex != 0) {

                              beginStopIndex = 0;

                              stream.Seek(-1, SeekOrigin.Current);

                        }

                  }

            }

            else if (!endFound) {

                  //load up the memorystream

                  if (backedUp) {

                        backedUp = false;

                  }

                  else {

                        xmpStream.WriteByte(b);

                  }

                  if (b == endPattern[endIndex]) {

                        endIndex++;

                        if (endIndex >= endPattern.Length) {

                              endFound = true;

                              xmpStream.SetLength(xmpStream.Length-endPattern.Length);

                              break;

                        }

                  }

                  else {

                        if (endIndex != 0) {

                              endIndex = 0;

                              stream.Seek(-1, SeekOrigin.Current);

                              backedUp = true;

                        }

                  }

            }

      }

}

if (!endFound) {

      Console.WriteLine("No XMP data found");

      break;

}

//load up the xmp

xmpStream.Position = 0;

XPathDocument xmpDocument = new XPathDocument(xmpStream);

XPathNavigator xmpNav = xmpDocument.CreateNavigator();

XmlNamespaceManager nsManager = new XmlNamespaceManager(xmpNav.NameTable);

nsManager.AddNamespace("x", "adobe:ns:meta/");

nsManager.AddNamespace("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");

nsManager.AddNamespace("iX", "http://ns.adobe.com/iX/1.0/");

nsManager.AddNamespace("crs", "http://ns.adobe.com/camera-raw-settings/1.0/");

nsManager.AddNamespace("exif", "http://ns.adobe.com/exif/1.0/");

nsManager.AddNamespace("aux", "http://ns.adobe.com/exif/1.0/aux/");

nsManager.AddNamespace("pdf", "http://ns.adobe.com/pdf/1.3/");

nsManager.AddNamespace("photoshop", "http://ns.adobe.com/photoshop/1.0/");

nsManager.AddNamespace("tiff", "http://ns.adobe.com/tiff/1.0/");

nsManager.AddNamespace("xap", "http://ns.adobe.com/xap/1.0/");

nsManager.AddNamespace("xapMM", "http://ns.adobe.com/xap/1.0/mm/");

nsManager.AddNamespace("dc", "http://purl.org/dc/elements/1.1/");

XPathExpression dateExpr = xmpNav.Compile("string(/x:xmpmeta/rdf:RDF/rdf:Description/exif:DateTimeOriginal)");

dateExpr.SetContext(nsManager);

string dateTimeStr = (string)xmpNav.Evaluate(dateExpr);

DateTime date = XmlConvert.ToDateTime(dateTimeStr);

 

 

posted on Sunday, February 12, 2006 2:48:18 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0]
 Thursday, February 09, 2006

Well, my new TV arrived yesterday afternoon, so after work, church, and getting Jenna to sleep, I finally got to hook it up.  The first thing I did was fire up the XBox360.  The dashboard just about knocked me across the room.  It is breathtakingly beautiful.  I fired up Halo 2 to check for video lag (for which I had to send my previous set back if you recall).  I was pleased to find minimal lag "out of the box".  A quick jaunt through the menus revealed "game mode", which eliminated the lag altogether (or at least to a point indistiguishable from my previous lag-free CRT set).

So I'm really looking forward to the HD Olympics coverage this year.  I hope it doesn't suck like it did 2 years ago for the summer games.  We'll see.

All said and done, I couldn't be more pleased.  We see what little annoyances I can find in a week.  I'm pretty picky.

(pictures coming)

posted on Thursday, February 09, 2006 7:54:29 AM (Pacific Standard Time, UTC-08:00)  #    Comments [0]
 Wednesday, February 01, 2006

I've had the itch for a new TV for a while now.  Mine is what I would call a "first-gen" HD set.  It's a 40" rear-projection CRT capable of 1080i scan rates.  As a CRT, it is subject to all the CRT problems:

  • Non-linear geometry problems across all 3 tubes - This could be summarized with "convergence issues", but causes lots of different visible anomalies.
  • Focus issues across three projectors
  • Blue gun "bleed"
  • "hot spots" of different color temperature across the screen.
  • Heavy as heck
  • etc.

Beyond that, as a first-gen set, it has some problems like not dealing with different aspect ratios cleanly, no digital inputs, etc.  In addition, it's getting old and is having some electronic problems.

Back in October, I ordered one of the new 1080p Sampsung DLP sets.  It was beautiful, but I sent it back for several reasons:

  • The DLP rainbow effect was very bothersome to me
  • The "video lag" made playing Halo 2 virtually impossible

I was very dissapointed to send it back, and I've been scared to get another one, for fear of the same dissapointments.  But I think I'm finally ready to get another one.  It's Sony's new 1080p SXRD set. It's based on Sony's implementation of LCOS (Liquid Crystal on Semiconductor).  It's a three-chip solution (separate chips for red, green, and blue), so it won't be able to do ClearType, and will have some convergence to deal with, but at that resolution, ClearType won't be necessary and any convergence problems will be linear and shouldn't be a big deal.  In addition, I have read lots of reviews that indicate there is no video lag with the xbox360.  I'd like to get personal experience with this, so I may cart my 360 to a store and try it out myself.

Anyone have any experience with Sony's SXRD sets?  Any advice?  I think the only thing I don't like about it is those crazy speakers sticking out the side.  All my audio runs through my sound system, so they'll just be there taking up space.

posted on Wednesday, February 01, 2006 12:05:35 PM (Pacific Standard Time, UTC-08:00)  #    Comments [1]
 Friday, January 27, 2006

Becky's been starting to teach lessons again.  This time in the evenings so that I can watch Jenna.  It's been difficult for me to be "on display" for her students and sometimes their parents.  So, I've been experimenting with different things to try while she's teaching.  Yesterday, Andrew was able to come by after his tennis tournament in Georgetown, so we decided to take Jenna out on the town.

We took her to Fry's first, where I loaded her up in the Baby Bjorn, and we walked the isles looking at all varieties of wonderful things.  She did very well.  In fact, she slept some.  She loves that thing.  When all else fails, I know I can settle her down in the "bjorn".  She did so well that it didn't matter that we hadn't brought the diaper bag or ANYTHING except for the bjorn.

On the way home, we swung by Pei Wei and picked up some dinner for ourselves and Becky.  Yummy.  Hopefully there will be many more trips to Frys and Pei Wei in our future.

posted on Friday, January 27, 2006 10:17:34 AM (Pacific Standard Time, UTC-08:00)  #    Comments [1]
 Thursday, January 26, 2006

Back the the days of .NET 1.1, you'd use a Hashtable to store values with a fast lookup using a key.  Hashtable used a single array of "buckets", which stored the key, value, and a collision index.  The index to the the bucket array is a transform of the hashcode of the key (unless there's a collision).  Long story short, there was no stable ordering to the values in a Hashtable (technically, the order is super-important to the algorithm, but it's not useful on the outside).

Fast-forward to 2.0, we have the Generic Dictionary.  It's algorithm is quite a bit different.  It has an array of Entry<Key, Value>, a private nested class similar to bucket in the Hashtable.  But, the "bucket" data is stored in a separate array of int, which holds the index into the Entry array for that hash.  When you add something to a Dictionary, it is simply added to the Entry array, and the bucket array is the one that's updated and possibly re-ordered.  Long story short, there IS meaning to the order you get from a generic Dictionary (using the enumerator).  It's the order you added them.  This subtle change adds alot of value to Dictionary in my opinion.

It was just a very interesting realization to me, and I thought someone else might find it useful.

posted on Thursday, January 26, 2006 9:03:31 AM (Pacific Standard Time, UTC-08:00)  #    Comments [0]
 Wednesday, January 25, 2006

I recently got mail at my domain working properly, and as a result, I've changed my main email address.  Of course, I'd like to get everything converted over.  One thing that's tied to my email address is my MS passport, which of course runs everything from my MSDN subscription to MSN Messenger to my XBox live account (and very well I might add).  Fortunately, you can change your email address from www.passport.com.  I had 2 issues when doing so.

First, I had tried out the windows live custom domains beta, which hooked my domain up with hotmail addresses.  It had some issues, so I had cancelled my service.  But, when I tried changing my email address, it said marklio.com was reserved and I couldn't use it.  Luckily, a few emails to passport and custom domains support cleared that up.

The second problem involves MSN Messenger. All passports have a unique ID underneath that is your actual "ID", even though you don't really see it anywhere.  This allows you to change the email address without changing "identities".  Most things migrated seamlessly (XBox360, etc).  And signing into messenger gave me my contacts list.  The problem was that none of my contacts were ever online.  As it turns out, the entries in the contacts list ARE tied to email addresses instead of the unique IDs, so everyone on my list has my old address in their list.

Booo.  So, if I'm on your MSN messenger contact list, please re-add me with my new email address.  It's mark at this domain (marklio.com).

posted on Wednesday, January 25, 2006 1:11:24 PM (Pacific Standard Time, UTC-08:00)  #    Comments [2]

Yes, the rumors are true.  Today is my birthday, and I am 30.

I know alot of people who got really depressed and overwhelmed when they turned 30.  I wasn't sure how I'd feel, but I can say now that I don't fell any different. That may have to do with Jenna being born last month.  That was such a huge change that 30 isn't that big of a deal.

I've gotten a monstrous barrage of silly e-Cards over the past few days... all from my dad.  Thanks.  And thanks to everyone else who has wished me well over the past few days.

posted on Wednesday, January 25, 2006 1:02:29 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0]
 Friday, January 20, 2006

No, I'm not talking about a BBS, although they are certainly obsolete as well. I'm talking about physical bulletin boards. I don't know about you, but where I work, there are tons of bulletin boards.  They just kind of blend into the walls.  Many have not been updated in years...YEARS!  For instance, the one closest to my cube has only a double-length sheet of paper on it with the following heading:

Employee Bulletin Board Ads

Sept 1, 2001

Whoa! 2001! As I walked around today, I looked at other boards and there was not a single one that had been updated in the last year.  This leads me to believe that bulletin boards are not only obsolete, they are dead.  Has anyone seen a working one anywhere?  I think the only place that might still see one is in a school.

posted on Friday, January 20, 2006 9:53:38 AM (Pacific Standard Time, UTC-08:00)  #    Comments [3]
 Wednesday, January 18, 2006

Whew! My blog's been down the last few days due to some weirdness.  This post is more or less to make sure everything's working again.

posted on Wednesday, January 18, 2006 1:31:24 PM (Pacific Standard Time, UTC-08:00)  #    Comments [2]