Mittwoch, 23. Mai 2012

Die Technik hinter smartLib

Wie wir in unserem letzten Post geschrieben haben, wollen wir gerne mit anderen Projekten auf der gleichen Datenbasis operieren. Was bedeutet das? Damit wollen wir erreichen, dass ihr eure Musik mit smartLib taggen und verwalten könnt - und die so vorgenommene Kategorisierung auch in anderen Programmen nutzen. Damit das funktioniert, wollen wir heute das Datenmodell vorstellen, das smartLib zugrunde liegt - um es mit euch zu diskutieren.

Alles in den Ordner

Wer sich smartLib bereits angeschaut hat, der weiß, dass wir Bibliotheksordner verwenden. Als Bibliotheksordner kann ein beliebiger Ordner dienen - also insbesondere auch solche, in denen die Musik sowieso schon liegt. In dem Ordner wird dann eine Datei hinterlegt, die Informationen über die enthaltenen Dateien beinhaltet. Dazu gehört auch der Dateipfad der einzelnen Lieder, relativ zum Bibliotheksordner. Das hat den Vorteil, dass ihr Eure Musiksammlung kreuz und quer kopieren könnt, von einem Rechner zum anderen, sogar smartLib löschen und neu herunterladen, ohne dass die Kategorisierung verloren geht: Hauptsache die Bibliotheksdatei verbleibt im Ordner.

Selbstverständlich soll es (anders als im Prototyp) möglich sein, mehrere Bibliotheksordner auf einmal einzubinden.

Die Bibliotheksdatei

Die oben beschriebene Datei trägt den festen Namen .soundlibrary und enthält die notwendigen Daten im XML-Format. Wir haben uns für XML aus drei Gründen entschieden:

1. Wenige Abhänigkeiten

Es gibt einige Alternativen zu XML, aber wenige davon sind so verbreitet. Mit so ziemlich jeder Programmiersprache wird auch schon Unterstützung für XML mitgeliefert und in den Fällen, in denen das nicht so ist, gibt es gute Bibliotheken. In jedem Fall sollte es für den Nutzer nicht notwendig sein, eine zusätzliche Software zu installieren - und das ist wichtig!

2. Validierbarkeit

Für ein XML-Format lassen sich vergleichsweise einfach Dokumente erstellen, die die zugelassene Struktur einer XML-Datei beschreiben. Das hat zwei Vorteile: Zunächst lässt sich sehr leicht überprüfen (validieren), ob eine Datei so aufgebaut ist, wie man es erwartet. Zum anderen kann dieses Dokument als Programmieranleitung dienen, wenn jemand .soundlibrary-Dateien mit seinem Programm einlesen möchte. Auch wenn eine solche Strukturbeschreibung für die im Prototyp verwendete Version der .soundlibrary-Datei noch nicht existiert, bevorzugen wir für diese Beschreibung XML Schema.

3. Lesbarkeit

XML ist eigentlich nicht dafür gedacht, von Menschen gelesen zu werden - aber es ist möglich. Das wird wohl nur für die technikaffinen Nutzer interessant sein, aber aber es soll durchaus vorkommen, dass man an einer solchen Datei etwas von Hand ändern will. Das ist mit XML möglich. Außerdem ermöglicht es eine einfache Fehlersuche, da die Datei einfach mit einem gänigen Texteditor geöffnet und geprüft werden kann.


Beispiel

Im Folgenden ein Auszug aus einer .soundlibrary-Datei.

<Library>
  ...
  <Sound ID="b23217894f27e64d2145c3a958b10a4a" Path="Erdenstern\Die Chronik Aventuriens\19 Auf zu neuen Abenteuern.mp3">
    <Taglist lang="unknown">
      <Tag>META/Album/Die Chronik Aventuriens</Tag>
      <Tag>META/Artist/Erdenstern</Tag>
      <Tag>META/Genre/Alternative Musik</Tag>
      <Tag>META/Title/Auf zu neuen Abenteuern</Tag>
    </Taglist>
  </Sound>
  <Sound ID="16dd21731ac526c5a649adba0bfdec64" Path="Erdenstern\Into The Dark\01 Intruder.mp3">
    <Taglist lang="unknown">
      <Tag>META/Album/Into The Dark</Tag>
      <Tag>META/Artist/Erdenstern</Tag>
      <Tag>META/Genre/Soundtracks</Tag>
      <Tag>META/Title/Intruder</Tag>
    </Taglist>
  </Sound>
  ...
</Library> 

Wie oben zu sehen, wird eine Bibliotheksdatei mit <Library>...</Library> umschlossen. Sie beinhaltet Sounds, die wiederum Taglists enthalten.

ID

Eine eindeutige Kennung, um die Datei zu identifizieren. Im Prototyp handelt es sich hier um den MD5-Hash der Datei.

Path

Der Pfad zur Datei, relativ zur Bibliotheksdatei. Grundsätzlich spräche nichts dagegen, hier auch absolute Pfade zuzulassen. Ob das einen Mehrwert bringt, ist aber fraglich.

lang

Kurz für language (Sprache). Wer eine Zeile tiefer sieht wird bemerken, dass die Tags in diesem Fall in Englisch gehalten sind. Es mag aber sinnvoll sein, Tags in verschiedenen Sprachen anzulegen. Das wird für den einzelnen Benutzer zuhause vielleicht nicht sonderlich bedeutend sein - wenn aber die gleiche Darstellung später für die Sounddatenbank benutzt werden soll, müssen unterschiedliche Sprachen berücksichtigt werden können. Da der Prototyp noch nicht nach Sprachen unterscheidet, ist der Wert hier auf "unknown" gesetzt.

Mögliche Erweiterungen und Änderungen

Nichts, was ihr hier bis jetzt gelesen habt, ist endgültig entschieden - deswegen stellen wir es ja auch vor. Dennoch haben wir uns schon Gedanken gemacht, wie das vorgestellte System noch erweitert oder verändert werden könnte.

Packages

Manchmal kann es nötig sein, dass zusammengehörige Sounds zusammengehörig bleiben. Von daher fänden wir es sinnvoll, einer Bibliothek auch Packages, also Pakete von Sounds hinzuzufügen. So ein Package wäre nichts weiter als eine Zip-Datei, die ihrerseits wieder eine .soundlibrary-Datei enthielte (sozusagen eine Bibliothek in der Bibliothek). Der Vorteil: Existiert eine neue Version von "Sounds für das Geisterschloss" kann das Package einfach ersetzt werden. Außerdem könnte man den gleichen Mechanismus nutzen, um einem Bekannten die eigene, kategorisierte und selbstverständlich freie Musiksammlung zukommen zu lassen. Der Nachteil: Entweder müsste der Player Pfade in Zip-Dateien verstehen (wie package.zip/Lied.mp3), oder die Bibliotheksanwendung müsste die Dateien auf Anfrage selbstständig in temporäre Ordner entpacken.

Trennung von Hash und ID

Bildet man zur endeutigen Identifikation einen MD5-Hash, dann ist dieser sehr starr. Denn: Das gleiche Lied, mit einer anderen Qualität aufgenommen oder in einem anderen Dateiformat, hat auch einen anderen MD5-Hash. Um also den Anwendungsfall "Ich möchte Datei A in meiner Bibliothek durch Datei B ersetzen" zu unterstützen müssen Hash und Identifikator getrennt sein. Dennoch können beide Werte im Regelfall gleich sein. Lädt man aber nun eine vorbereitete Szene herunter, die wiederum auf genau diese Datei verweist, wird der Hashwert wieder benötigt (und nicht der veränderte Wert von ID). Demnach müssten beide Werte, Hash und ID, in die Bibliothek übernommen werden.

Aufbau der Tags

Die Tags sind, wie im Beispiel oben zu sehen, hierarchisch. Dass in META/Artist/Erdenstern das "META" groß geschrieben ist, deutet aber bereits an, dass es sich bei der ersten Stufe um eine besondere handelt. Wir wollen damit zum Ausdruck bringen, dass diese Metainformationen zu Interpret, Album, Titel, u.Ä. irgendwie anders sind als Tags wie Atmosphere/Fight. Solche Metainformationen möchte man möglicherweise anders behandeln oder darstellen, als normale Tags. Das hat uns auf die Idee gebracht, dieses System auszuweiten. Solche Kategorien von Tags könnten META, MUSIC und EFFECT sein und die zugehörigen Tag-Bäume in smartLib in unterschiedlichen Registerkarten angezeigt werden. Um das Absetzen der Tag-Kategorie vom eigentlichen Tag weiter zu betonen, würden wir Kategorie und Tag nicht durch Slash, sondern durch Doppelpunkt trennen, also META:Artist/Erdenstern und MUSIC:Atmosphere/Fight.

Name der Bibliothek

Wenn mehrere Bibliotheksordner verwendet werden, mag es sinnvoll sein, einer Bibliothek einen Namen zuzuordnen. Hierzu könnte dem <Library/>-Tag ein Attribut Name hinzugefügt werden.

<Ignore/>

Zuletzt noch eine kleine Erweiterung: Möglicherweise möchte ein Nutzer nicht alle Audiodateien eines Ordners auch zur Bibliothek hinzufügen. Wie aber unterscheidet man diese Dateien von neuen Dateien in einem Ordner? Hierzu könnte man die Dateien, die explizit nicht verwendet werden sollen, mittels eines Eintrags in der .soundlibrary-Datei ignorieren. Fraglich ist, ob auch nicht-Audiodaten auf "ignore" gesetzt werden sollen. Der Prototyp zu smartLib beispielsweise nimmt generell nur Dateien auf, deren MIME-Type mit audio beginnt. Ein explizites Ignorieren von Bildern oder ähnlichen Dateien wäre daher unnötig.

Abschließendes

Das waren unsere Ausführungen zu den technischen Grundlagen von smartLib. Wir hoffen, dass diejenigen unter euch, die mit ihrem eigenen Projekt eine Kooperation mit smartSound/smartLib anstreben, nützliche Informationen gefunden haben und jetzt mit uns eine lebhafte Diskussion beginnen. Und vielleicht gibt es ja auch den ein oder anderen, der das Ganze einfach nur aus Neugier gelesen hat.

Im nächsten Blogpost erwartet euch übrigens ein Ausblick auf smartSound 0.2 und - ganz wichtig - eine Abstimmung über die Reihenfolge der Features, die wir als nächstes in smartSound einbauen wollen. Bis dahin freuen wir uns über jede Form von Lob, Kritik und Verbesserungsvorschlägen - sowohl hier im Blog als auch in den einschlägigen Foren.

Kommentare:

  1. Bitte überlegt noch einmal, ob ihr nicht doch eine SQLite-Datenbasis statt einer XML-Datei nehmen wollt. Vergleichen wir das mal miteinander:

    - Abhängigkeiten: gleich, man braucht bei SQLite auch nur eine Bibliothek im Programm selbst, und die gibt's unter anderem für Java, .NET, C++, ...

    - Validierbarkeit: im Prinzip gleich; eine DB muss man nicht selbst validieren und man kann einfach das Datenbasis-Schema veröffentlichen.

    - Lesbarkeit: die DB ist natürlich in Rohform nicht lesbar, aber es gibt Tools, mit denen man die Daten per direkter SQL-Query anschauen oder auch ändern kann. Formatfehler selbst dürften bei der DB nicht vorkommen, da SQLite millionenfach getestet ist.

    - Speichern in einer Datei: bei beiden gleich.

    - Erweiterbarkeit / Änderbarkeit: ähnlich. Nur Attribute oder neue Klassen hinzufügen geht bei beiden gut, bei größeren Änderungen (die im Sinne der Kompatibilität ab einem gewissen Stand sowieso sehr selten sein sollten) muss man bei beiden eine Konvertierung schreiben.

    Warum also eine DB, wenn die bisherigen Kriterien alle gleich sind? Erstens weil man kaum "Middleware" machen muss. Weder braucht man eigene Container für Business-Objekte noch eigene Verknüpfungen zwischen diesen. Abfragen wie "gib mir alle Musikstücke, welche das Tag 'düster' oder das Tag 'geheimnisvoll' haben" oder "gib mir alle englischen Tags" werden viel einfacher.

    Zweitens weil Kreuzverknüpfungen einfacher werden. Man stelle sich z.B. vor, jemand, der deutsch und englisch kann, will für seine englischen Freunde bestehende Tags übersetzen. Dabei will er natürlich nicht alle Dateien neu taggen. Damit das Schema das unterstützt, braucht man Tags als eigene Objekte mit IDs und bei den Dateien eine Referenz auf die Tag-ID statt auf die lokale Übersetzung des Tags selbst. Sowas kann man zwar auch in XML reinmachen, aber das wird dann eigentlich eine relationale DB in XML-Format abgespeichert, und dann kann man besser auch gleich eine DB nehmen.

    AntwortenLöschen
  2. Ein Hash als ID zu verwenden halte ich nicht für sinnvoll, ich glaube, wenn zwei Leute die gleiche CD rippen, werden die entstehenden Dateien sehr häufig unterschiedlich.

    Mögliche Ansätze könnten die Verwendung von MusicBrainz, Echoprint oder ähnlichem sein. Oder vielleicht ein fehlertoleranter Algorithmus, der Autor und Titel vergleicht, aber das könnte schwierig sein.

    AntwortenLöschen
  3. Hi,

    kurz zum aktuellen Stand: Wegen SQLite vs. XML werden wir uns nochmal zusammen setzen und das in Ruhe überdenken - und natürlich deine Argumente mit einfließen lassen. Das wird aber wahrscheinlich ein paar Tage dauern bis wir da zu einem Ergebnis kommen.

    Was die Hash-IDs angeht: Die sind vor allem dazu gedacht, die Lieder, die wir später (irgendwo online) in der Datenbank haben, eindeutig zuordnen zu können. Aber du hast natürlich recht, dass das nicht reicht. Über einen intelligenten Algorithmus der dem Nutzer Musikstücke mit ähnlichen (META-)Tags anbietet haben wir auch schon nachgedacht. Die Idee mit MusicBranz oder Echoprint ist wirklich verdammt gut und wir sollten in der Definition dafür definitiv Platz lassen. Ob wir es aber von Anfang an einbauen können, ist natürlich immer noch fraglich.

    AntwortenLöschen
  4. Hi Jörg,

    eigentlich sollte der Kommentar hier schon gestern Abend stehen, entschuldige die Verspätung. Für alle, die so mitlesen: Achtung, es wird technisch!

    Wir haben uns vorgestern in anderthalb Stunden lebenhafte Skype-Diskussion begeben. Wir haben das ein wenig rollenverteilt gemacht: Einer hat sich auf die XML-Seite, der andere auf die SQLite-Seite gestellt. Damit haben wir sichergestellt, dass wir da nicht voreingenommen rangehen.

    Bei den Dingen, die du aufführst, hast du durchaus recht, aber so manche kann man nicht ohne ein "aber" stehen lassen. So zu Abhängigkeiten: Der Punkt, den wir da aufgeführt haben, war eben, dass keine zusätzlichen Bibliotheken gebraucht werden. Mindestens bei Java und .NET befindet sich XML-Support bereits in der Standardbibliothek, es besteht also die Sicherheit, dass diese Implementierungen standardkonform (und kompatibel mit anderen) funktionieren. Bei Third-Party Bibliotheken ist das immer so eine Sache. Aber auch hier gibt es Ausnahmen: Nach unseren Recherchen ist etwa in Python SQLite-Support direkt mit eingebaut, dafür muss XML nachgerüstet werden.

    "Einfachere Abfragen": Auch das ist nicht so ganz richtig. SQL ist weit verbreitet, bekannt und bequem, aber auch für XML gibt es sowas. Alle unterschiedlichen Tags in Englisch? Kein Problem mit XPath: distinct-values(//Taglist[@lang='en']/Tag/text()). Was da die 'einfachere' Lösung ist, hängt wohl vor allem damit zusammen, wie viel man mit einer Technik schon gearbeitet hat.

    Diese Punkte lassen sich aber durchaus relativieren, wie wir gemerkt haben und am Ende waren wir wieder bei Null herausgekommen, was denn nun besser sei - XML oder SQLite. Es gibt aber eine Sache, die dann doch den Ausschlag zu XML gegeben hat: Viele Bibliotheksordner.

    Hat ein Nutzer mehrere Bibliotheksordner, so ist das mit XML relativ einfach zu lösen. Die geparsten Dokumente werden als DOM einfach aneinander gehängt - man kann also den ganzen Baum ziemlich einheitlich verarbeiten. Die einzige Zusatzinfo, die man braucht, ist wenn man aus dem relativen einen absoluten Pfad machen will. Was aber das wichtigste ist: Wurden Änderungen eingefügt an den Elementen, Tags hinzugefügt, Sounds entfernt und so weiter, dann muss man sich nicht merken wo das passiert ist. Man nimmt am Ende Baumstruktur wieder in die einzelnen Bibliotheken auseinander und schreibt die einzeln wieder zurück auf die Platte. Mit etwas Intelligenz kann man auch nur die Bibliotheken abspeichern, in denen etwas geändert wurde.

    Im Kontrast mit SQLite: Hier haben wir N verschiedene Datenbanken, jede mit ihren eigenen Primary Keys. Die Daten lassen sich also auch nicht einfach in eine große Datenbank zusammenmergen - und wenn man das doch machen will, dann muss man das zu jedem Programmstart machen und sehr aufwendig. Tut man das nicht, so müssen für N Bibliotheksordner auch N SQL-Queries abgeschickt werden. Aber egal ob große Datenbank oder N kleine: Zum Ergebnis jeder Abfrage, etwa einer Liste von Sounds, muss für jeden Eintrag im Ergebnis mitgespeichert werden, aus welcher Datenbank er ursprünglich kommt. Denn bei einer Änderung muss der veränderte Eintrag wieder auf seine Ursprungsdatenbank zurückgemapt werden. Hier geht der große Vorteil von Datenbanken verloren: Auf einmal braucht man also doch wieder "Business-Objekte" und kann die Daten nicht einfach direkt aus der Datenbank einlesen, ohne sie in eine andere Form bringen zu müssen. Da ist XML sogar besser geeignet, weil diese Struktur implizit durch den XML-Document-Tree gegeben ist. Der Nachteil dabei ist natürlich, dass dieser im Speicher bleiben müsste.

    Wie du siehst, haben wir es uns nicht leicht gemacht mit der Entscheidung für XML. Aber wir haben ja gesagt, dass uns Zusammenarbeit mit anderen Projekten sehr wichtig ist und dabei möchten wir bleiben. Wenn dich die Ausführungen hier also nicht überzeugt haben sollten (was wir nicht hoffen), dann werden wir zusätzlich zu unseren XML-Daten noch einen (automatischen) SQLite-Export unserer Daten anbieten.

    Gruß,
    André

    AntwortenLöschen