Dojos für Entwickler 2. Stefan Lieser
Personen versehen möchte. Da können einige Personen zusammenkommen, sodass es sinnvoll ist, beispielsweise Personen/Familie/Stefan von Personen/Freunde/Stefan zu unterscheiden.
Adobe hat hierarchische Schlagworte erstmalig in den XMP-Daten abgelegt. An diese „Vorlage“ sollten sich nun alle Softwarehersteller halten.
Die Aufgabe für diesen Monat lautet: Durchsuchen Sie ein Verzeichnis, und listen Sie alle gefundenen Schlagworte in einer Hierarchie auf. Die Auflistung kann auf der Konsole erfolgen. Genauso gut können Sie aber auch eine grafische Benutzerschnittstelle verwenden und ein TreeView-Control entsprechend füllen.
Um an Testdaten zu kommen, können Sie die Fotoverwaltung Ihrer Wahl verwenden. Am Anfang steht „Forschungsarbeit“, um herauszufinden, wie genau nun die Schlagworte in den Exif-, IPTC-, XMP- oder sonstigen Metadaten abgelegt sind. Und natürlich müssen Sie den Zugriff auf die JPEG-Dateien nicht selbst programmieren, für diesen Zweck gibt es fertige .NET-Bibliotheken. Selbst im .NET Framework ist ab 3.0 dazu einiges vorhanden.
Wer mag, kann die Übung dann erweitern: Listen Sie die Dateinamen aller Fotos, die mit einem gewählten Schlagwort versehen sind. Viel Spaß beim Durchforsten Ihrer Fotosammlung.
[ml]
Lösung 2
JPEG-Metadaten auslesen
Ausgezeichnete Fotos
Mit den Formaten Exif und IPTC können Sie JPG-Dateien Stichworte zuordnen. Viele Hersteller von Grafikprogrammen nutzen dieses Format aber nicht. Dieses und weitere Probleme löst Stefan auf seinem Weg zur digitalen Fotosammlung.
Dieses Mal begann die Übung für mich mit einem Spike. Ich hatte zwar eine grobe Vorstellung davon, wie die Metadaten mithilfe von .NET-Bordmitteln aus den JPEG-Dateien ausgelesen werden können. Aber schon die Frage, welche Assemblies dafür referenziert werden müssen, konnte ich nicht beantworten. Folglich habe ich als Erstes ein Spike-Projekt in meiner Visual-Studio-Solution angelegt. Darin habe ich so lange herumprobiert, bis ich einige Metadaten aus einer JPEG-Datei auf der Konsole ausgeben konnte. Dabei habe ich gelernt, welche Assemblies referenziert werden müssen, es sind:
PresentationCore,
WindowsBase,
System.Xaml.
Nach dem Hinzufügen dieser drei Referenzen, die alle aus dem Bereich WPF stammen, konnte ich mit meiner kleinen Minianwendung starten. Bei meinen Spikes verwende ich üblicherweise NUnit [1], um mithilfe des ReSharper-Unit-Test-Runners [2] eine Methode innerhalb von Visual Studio ausführen zu können. Typischerweise werden für solche Zwecke wohl eher Konsolenanwendungen erstellt. Ich bevorzuge die Variante über Tests, weil ich so in meinem Spike-Projekt viele Methoden unterbringen kann, die ich mit dem Test-Runner bequem einzeln starten kann. Bei einer Konsolenanwendung müsste man sich in solchen Fällen Gedanken machen, wie man unterschiedliche Teile eines Spikes ausführt. Ein kleines Menü wäre denkbar, oder auch das Auskommentieren einzelner Teile. Für mich ist das Starten über den Testrunner die einfachste Variante. Ausgaben auf der Konsole sind natürlich trotzdem möglich.
Metadaten lesen
Mein erster Spike schreibt alle Metadaten, die über Properties der Klasse BitmapMetadata zur Verfügung stehen, auf die Konsole, siehe Listing 1. Gestartet wird diese Methode durch eine kleine Testmethode, die Listing 2 zeigt.
Listing 1
Metadaten ausgeben.
public class Exif { public static void ShowExif(string filename) { BitmapSource img = BitmapFrame.Create( new Uri(filename, UriKind.Relative)); var metadata = ( BitmapMetadata)img.Metadata; Console.WriteLine( metadata.CameraManufacturer); Console.WriteLine(metadata.CameraModel); Console.WriteLine(metadata.Author); Console.WriteLine(metadata.DateTaken); Console.WriteLine(metadata.Format); foreach (var keyword in metadata.Keywords) { Console.WriteLine(keyword); } Console.WriteLine(metadata.Location); Console.WriteLine(metadata.Rating); Console.WriteLine(metadata.Subject); Console.WriteLine(metadata.Title); } }
Listing 2
Den Spike starten.
[TestFixture] public class RunDemos { [Test] public void Spike_1() { Exif.ShowExif(@"..\DSCF1243.JPG"); } }
Die Verwendung der Klasse BitmapMetadata hat den Vorteil, dass sie sehr leicht zu bedienen ist. Das liegt vor allem daran, dass die wichtigsten Metadaten aus der JPEG-Datei als Properties unmittelbar zur Verfügung stehen.
Das gilt allerdings nicht für alle Metadaten. Denn das Modell der Metadaten muss natürlich so flexibel sein, dass es erweitert werden kann, ohne dass in diversen Frameworks zusätzliche typisierte Properties ergänzt werden müssten. Folglich musste man sich für den Zugriff auf die Metadaten einen allgemeineren Ansatz überlegen. Die Metadaten einer Bilddatei werden jeweils über einen Pfad identifiziert. Mithilfe des Metadaten-Pfades und der Methode GetQuery können sämtliche Metadaten ausgelesen werden:
var value = metadata.GetQuery( "/ifd/iptc/keywords");
Auf diese Weise lassen sich nun zwar alle Metadaten auslesen, allerdings hat dieser generische Ansatz den Nachteil, dass die gelieferten Werte dann vom Typ object sind. Man muss die Werte daher gegebenenfalls auf den korrekten Typ casten.
Erstaunen und Ärger
Nach diesen Spikes dachte ich, es könne mit der Lösung der Aufgabenstellung losgehen. Doch es zeigte sich, dass nicht alle Programme dasselbe Verfahren zur Ablage von Stichwörtern verwenden. Für meine Spikes hatte ich mit dem Windows Explorer Stichwörter an JPEG-Dateien gesetzt. Abbildung 1 zeigt die Dateieigenschaften.
[Abb. 1]
Stichwörter mit dem Windows Explorer vergeben.
Diese Stichwörter lassen sich mit dem im Spike gezeigten Verfahren über metadata.Keywords leicht auslesen. Doch schon mein erster Versuch, die Stichwörter eines Fotos auszulesen, die ich mit dem Adobe Photoshop Elements Organizer an die Datei gesetzt hatte, schlug fehl. Eine kurze Kontrolle mit dem Windows Explorer zeigte: Auch hier keine Spur von Stichwörtern zu sehen. Das liegt daran, dass der Elements Organizer die Stichwörter in einer eigenen Datenbank ablegt. Dagegen ist ja an sich nichts einzuwenden. Ich vermute mal, dass die Suche in einem großen Fotobestand ohne eigene Datenbank sehr langsam wäre. Doch was mir nicht einleuchten will, ist die Tatsache, dass der Elements Organizer die Stichwörter nicht parallel auch in der JPEG-Datei ablegt. Zwar kann man das durch einen Export erreichen, doch den muss man explizit starten. Und wenn man anschließend Stichwörter oder deren Zuordnung ändert, muss man diese erneut exportieren.
Eine kurze Recherche erbrachte, dass Adobe nicht der einzige Hersteller ist, der diesen Weg gewählt hat. Ich halte das für keine gute Lösung, denn dadurch wird der Zugriff auf einen verschlagworteten Fotobestand von mehreren Arbeitsplätzen aus oder auch mit unterschiedlichen Programmen unnötig verkompliziert. Besonders ärgerlich finde ich, dass die eigens für Stichwörter vorgesehenen Metadaten in den Bilddateien nicht genutzt werden. Dann hätte man sich die ganze Standardisierung auch gleich sparen können. Ein weiteres Ärgernis: Beim Export der Stichwörter ignoriert Adobe leider die Hierarchie und exportiert die Stichwörter ohne die Struktur. „Motive/Kirchen“ wird zu „Kirchen“, „Personen/Familie/Stefan“ wird zu „Stefan“. Das ist sehr schade.
Erste Iteration
Als mein Ärger über diese unschöne Vorgehensweise der Softwarehersteller etwas verflogen war, habe ich begonnen, die erste Iteration der Lösung zu entwickeln. In der ersten Iteration möchte ich ein Verzeichnis rekursiv durchsuchen und die Stichwörter der gefundenen Dateien auf der Konsole ausgeben. Natürlich sollen dabei mehrfach vorkommende Stichwörter