ID3-Tags aus Musikdateien auslesen - Teil 1 (ID3v1, Javascript)

Schon seit langer Zeit bietet sich die Möglichkeit an, seine Musik mit bestimmten Tags zu versehen. Mittlerweile gibt es für fast jede Platform und Programmiersprache entsprechende Bibliotheken, um einfach und schnell ID3 Tags auszulesen. Wie genau die Tags aus der Datei ausgelesen werden können, möchte ich in dieser Tutorialreihe aufzeigen.

ID3v1 und ID3v2

Bevor wir beginnen, muss ein kleines Verständnis für die verschiedenen Versionen der ID3-Tags aufgebaut werden. Die erste Version ist ID3v1 und sein Nachfolger ID3v1.1. Diese Version wird in den letzten 128 Bytes der Datei abgespeichert und hat somit eine festgelegte Größe. IDv2 und seine 3 Nachfolger besitzen diese Beschränkung nicht. Sie werden am Anfang der Datei abgespeichert, ohne eine feste Bytezahl.

ID3v1 auslesen

Bevor es zum Code geht, ist es erst wichtig die Theorie zu verstehen. Eine Datei besteht aus vielen Bytes die von der Software mit der sie geöffnet wird interpretiert werden. Das bedeutet, die ID3-Tags sind als HEX-Code / Zeichen in der .mp3 Datei zu finden. Um genau zu sein, findet man sie in den letzten 128 Bytes. Doch hier sind die genauen Fakten:

  • ID3v1 Tags werden mit den Zeichen "TAG" eingeleitet
  • [30] Song Titel
  • [30] Interpret
  • [30] Album
  • [4] Jahr
  • [30] Kommentar
  • [1] Genre

Jeder Zahlenwert entspricht der Bytezahl (d.h. die ersten 30 Bytes sind der Titel etc.).

Jetzt brauchen wir ein kleines Grundgerüst, um mit JavaScript arbeiten zu können:

<!DOCTYPE html>  
<html>  
<body>

<input type="file" id="mp3" name="file">  
<div id="tags"></div>  
var tagDiv = document.getElementById("tags");

<script type="text/javascript">  
    var input = document.getElementById("mp3");

    var file = input.files[0];
    input.onchange = function() {
        var fileReader = new FileReader();
        fileReader.onload = getText;
        var ok = fileReader.readAsBinaryString(file);
    }

    function readFile(e) {
        var tagString = e.target.result;

        //HIER GEHT ES WEITER!

    }


</script>  
</body>  
</html>  

Ab hier geht es erstmal bei "//HIER GEHT ES WEITER!" weiter. Das bedeutet, wenn nicht anders geschrieben, muss jeder kommende Code dort eingefügt werden.

Was passiert bei diesem simplen Formular genau?
Es wird ein einfaches Input-Feld erstellt, in welchem eine Datei ausgewählt werden kann. Diese Datei wird ausgewählt, und der FileReader interpretiert den Code der Datei.

Als nächstes müssen wir den Anfang der TAGs finden: "TAG".
Wir fügen ein:

tagString = tagString.substring(tagString.indexOf("TAG") + 3);  
//Entferne den String vor dem "TAG" Indikator ab und entferne das "TAG" selbst.

Jetzt steht der String mit allen Tags bereit. Wir müssen nur noch alle auslesen:

var songTitle = tagString.substring(0, 30);  
var songInterpret = tagString.substring(30, 60);  
var songAlbum = tagString.substring(60, 90);  
var songJahr = tagString.substring(90, 94);  
var songKommentar = tagString.substring(94, 123);  
var songGenre = tagString.substring(124);

//Ausgeben

console.log("Titel: " + songTitle);  
console.log("songInterpret: " + songInterpret);  
console.log("songAlbum: " + songAlbum);  
console.log("songJahr: " + songJahr);  
console.log("songKommentar: " + songKommentar);  
console.log("songGenre: " + songGenre);  

Einige der ID3-Tags werden jetzt richtig ausgegeben (Beachte: Es werden nur ID3v1 Tags gelesen).
Trotzdem gibt es noch ein paar komische Zeichen, inklusive das Genre.
Das ganze ist eigentlich ganz einfach zu verstehen. Wir wissen, dass beispielsweise der Titel-Tag genau 30 Zeichen lang ist. Hat man die ganzen Zeichen aber nicht ausgenutzt, wird in der Datei der HEX-Code "00" gespeichert, für welches es kein UTF-8 Equivalent gibt. Die Folge: Komische Zeichen. Auch das Genre muss noch vom HEX Code zu einer Zahl umgewandelt werden.

Wir entfernen also erstmal mit einer ganz einfachen Funktion, alle HEX "00" Werte von den Tags.

function cutZero(cutMe) {  
    //Neuer Wert newVal
    var newVal = "";

    //Falls HEX Wert nicht 0, füge Char in neuen Wert newVal
    for(var i = 0; i < cutMe.length; i++) {
        if(cutMe.charCodeAt(i).toString(16) != '0') {
            newVal += cutMe.slice(i, i+1);
        }
    }
    return newVal;
}

Jetzt bauen wir die Funktion in den Code von vorhin ein:

console.log("Titel: " + songTitle);  
console.log("songInterpret: " + songInterpret);  
console.log("songAlbum: " + songAlbum);  
console.log("songJahr: " + songJahr);  
console.log("songKommentar: " + songKommentar);  
console.log("songGenre: " + songGenre);  

Die Tags werden nun richtig ausgegeben, bis auf das Genre. Diese ist ein wenig Problematischer, denn das Genre besteht nur aus einer HEX Zahl (Dezimal von 0 - 149).
Um diese in die richtige Dezimalzahl umzuwandeln, fügen wir unter ihre Initialisierung folgenden Code ein:

//songGenre = Integer von HEXCode, umgewandelt in Dezimalzahl
songGenre = parseInt(songGenre.charCodeAt(0).toString(16), 16);  

Die Liste der zugehörigen Genre-Ziffern sind hier als Array zu finden: https://github.com/TooTallNate/id3-genre/blob/master/genres.json

Momentan können wir die kompletten ID3v1 Tags in der Konsole ausgeben.

ID3v1.1

In ID3v1.1 wurde der "Album track"-Tag eingeführt. Diesen findet man jetzt im vorletzten Byte (Vor dem Genre-Tag).

Um auf diesen zuzugreifen, fügen wir folgenden Code, vor der Initialisierung des Genre-Tags ein:

var songAlbumTrack = cutZero(tagString.substring(123));  
if(songAlbumTrack != "") {  
    songAlbumTrack = parseInt(songAlbumTrack.charCodeAt(0).toString(16), 16);
}

Außerdem geben wir diesen Tag ebenfalls in der Konsole aus:

console.log("songAlbumTrack: " + songAlbumTrack);  

Das war's! Jetzt kann man die Seite erweitern, in dem man die Tags im HTML Dokument ausgibt.

Im nächsten Teil wird es darum gehen, ID3v2-Tags auszulesen (Inkl. Bildern) und die beiden voneinander zu unterscheiden.

Der komplette Code:

<!DOCTYPE html>  
<html>  
<body>  
    <input type="file" id="mp3" name="file">
    <div id="tags"></div>
    <script type="text/javascript">
        var input = document.getElementById("mp3");
        var tagDiv = document.getElementById("tags");

        var file = input.files[0];
        input.onchange = function() {
            var fileReader = new FileReader();
            fileReader.onload = getText;
            var ok = fileReader.readAsBinaryString(file);
        }

        function getText(e) {
            var tagString = e.target.result;
            tagString = tagString.substring(tagString.indexOf("TAG") + 3);

            var songTitle = cutZero(tagString.substring(0, 30));
            var songInterpret = cutZero(tagString.substring(30, 60));
            var songAlbum = cutZero(tagString.substring(60, 90));
            var songJahr = cutZero(tagString.substring(90, 94));
            var songKommentar = cutZero(tagString.substring(94, 123));

            var songAlbumTrack = cutZero(tagString.substring(123));
            if(songAlbumTrack != "") {
                songAlbumTrack = parseInt(songAlbumTrack.charCodeAt(0).toString(16), 16);
            }

            var songGenre = cutZero(tagString.substring(124));
            songGenre = parseInt(songGenre.charCodeAt(0).toString(16), 16);

            console.log("Titel: " + songTitle);
            console.log("songInterpret: " + songInterpret);
            console.log("songAlbum: " + songAlbum);
            console.log("songJahr: " + songJahr);
            console.log("songKommentar: " + songKommentar);
            console.log("songAlbumTrack: " + songAlbumTrack);
            console.log("songGenre: " + songGenre);

        }

        function cutZero(cutMe) {
            var newVal = "";
            for(var i = 0; i < cutMe.length; i++) {
                if(cutMe.charCodeAt(i).toString(16) != '0') {
                    newVal += cutMe.slice(i, i+1);
                }
            }
            return newVal;
        }
    </script>
</body>  
</html>