Online Music-Stores à la Musicload [1] erleben derzeit einen unglaublichen Boom und erfreuen sich großer Beliebtheit. Um Informationen über eine MP3-Datei anzuzeigen, wird dabei noch nicht einmal die Unterstützung einer Datenbank benötigt. Wie das möglich ist und wie Ihnen PHP bei der Erstellung eines eigenen Musik-Katalogs helfen kann, erfahren Sie in diesem Artikel.
Woher kennt ein MP3-Player wie Winamp den Interpreten oder Titel des Songs, den Sie gerade abspielen? Selbst Informationen über das Album, auf dem der Song veröffentlicht wurde, kann er Ihnen verraten. Nein, das ist keine Hexerei! Diese Informationen stehen in der Datei selbst, sofern sich ein Benutzer oder ein Programm die Zeit genommen hat, diese Daten in die Datei zu schreiben. Auch Musikdateien anderer Formate, wie WMA oder Ogg Vorbis, können solche Informationen enthalten; im Rahmen dieses Artikels werden wir aber nur MP3-Dateien betrachten.
ID3-Tags
Die MP3-Spezifikation definiert zwar die Art und Weise der Speicherung von Musikdaten, sieht aber keine Möglichkeit vor, Metadaten wie Titel oder Interpret zu speichern. Um diese Einschränkung zu umgehen, wurde der ID3-Standard [2] eingeführt. Die Metadaten werden in so genannten ID3-Tags gespeichert, welche sich, abhängig von der Version des verwendeten ID3-Standards, am Anfang oder am Ende der Musikdatei befinden. Die einfachste Form stellen die ID3-Tags in der Version 1, kurz ID3v1-Tags, dar. Die Informationen über die Musikdaten befinden sich hierbei in den letzten 128 Byte der Datei. Eingeleitet vom String 'TAG', folgen Informationen über Titel (30 Zeichen), Interpret (30 Zeichen), Album (30 Zeichen), Jahr der Aufnahme (vierstellige Jahreszahl), Kommentar (30 Zeichen), Genre des Songs (1 Byte). Ein Tag mit dieser Struktur bezeichnet man als ID3v1.0-Tag. Im Unterschied dazu gibt es noch ID3v1.1-Tags, welche man wesentlich häufiger antrifft, da diese noch zusätzlich Informationen über die Tracknummer eines Songs innerhalb eines Albums speichern können. Dazu wird der Platz für den Kommentar auf 28 Zeichen verringert, danach folgt ein Null-Byte, und das danach stehende Byte repräsentiert die Tracknummer. Die Gesamtlänge des Tags bleibt bei 128 Byte. In den Abbildungen 1 und 2 sehen Sie eine grafische Darstellung dieser Strukturen.

Abb. 1: Struktur eines ID3v1.0-Tags

Abb. 2: Struktur eines ID3v1.1-Tags
PEAR hilft!
Um ID3v1-Tags auszulesen, existiert in PEAR [4][5] bereits ein Package mit dem Namen MP3_Id [3]. Damit sind Sie problemlos in der Lage, Informationen aus einem Tag zu lesen oder gegebenenfalls zu schreiben. Wenn eine Datei noch kein ID3-Tag besitzt, so wird eines erzeugt. In Listing 1 sehen Sie, wie man die Informationen eines Tags auslesen kann. Man erzeugt sich eine Instanz der Klasse MP3_Id, liest die Datei ein und holt mit der Methode getTag() die Daten, die in den einzelnen Feldern gespeichert wurden, um sie weiter zu verarbeiten. Listing 2 zeigt die Ausgabe von Listing 1. Eine Übersicht der verfügbaren Feldnamen finden Sie in der Endbenutzer-Dokumentation des Packages auf der PEAR-Homepage.
Listing 1: <?php
require_once 'MP3/Id.php';
// Instanz erzeugen und Datei lesen
$id3 = &new MP3_Id();
$result = $id3->read('../data/Little-Big-Man.mp3');
if (PEAR::isError($result)) {
die($result->getMessage() . "\n");
}
// Feldwerte lesen und ausgeben
echo 'Titel: ' . $id3->getTag('name') . "\n";
echo 'Interpret: ' . $id3->getTag('artists') . "\n";
echo 'Album: ' . $id3->getTag('album') . "\n";
echo 'Jahr: ' . $id3->getTag('year') . "\n";
echo 'Kommentar: ' . $id3->getTag('comment') . "\n";
echo 'Genre: ' . $id3->getTag('genre') . "\n";
echo 'Genre-Nummer: ' . $id3->getTag('genreno') . "\n";
echo 'Track: ' . $id3->getTag('track') . "\n";
?>
Listing 2: Titel: Little Big Man
Interpret: Dirty Mac
Album: Demo-Tape
Jahr: 2001
Kommentar: Song vom Demo-Tape
Genre: Rock
Genre-Nummer: 17
Track: 5
Wie Listing 3 beweist, ist es genauso einfach, die Informationen des ID3-Tags zu ändern bzw. eine Datei mit einem Tag zu versehen. Einfach (wie in Listing 1 bereits gesehen) eine Instanz der Klasse MP3_Id erzeugen, Datei einlesen, beliebige Informationen mithilfe der Methode setTag($feldname, $wert) setzen und zu guter Letzt die Informationen in die Datei schreiben. Sollten Sie der ganzen Taggerei überdrüssig sein, so sehen Sie in Listing 4, wie sie sämtliche Tags aus einer Datei entfernen. Hierzu verwendet man die Methode remove(), den Rest kennen Sie mittlerweile. MP3_Id bietet darüber hinaus noch weitere Methoden, welche es Ihnen ermöglichen, ID3-Tags von einer Datei in eine andere zu übernehmen oder sich ein Array der existierenden Genres ausgeben zu lassen. Ein Blick in die Dokumentation lohnt sich auf jeden Fall!
Listing 3: <?php
require_once 'MP3/Id.php';
// Instanz erzeugen und Datei lesen
$id3 = &new MP3_Id();
$result = $id3->read('../data/Little-Big-Man.mp3');
// Fehler "Tag not found" wird ignoriert
if (PEAR::isError($result) && $result->getCode() !== PEAR_MP3_ID_TNF) {
die($result->getMessage() . "\n");
}
// Informationen setzen
$id3->setTag('name', 'Neuer Titel');
$id3->setTag('artists', 'Andere Band');
$id3->setTag('album', 'Schlagertraum #3');
$id3->setTag('year', 1984);
$id3->setTag('comment', 'Volksmusikal. Hochgenuss');
$id3->setTag('genre', 'Folk');
$id3->setTag('track', 5);
// Tag schreiben
$result = $id3->write();
if (PEAR::isError($result)) {
die($result->getMessage() . "\n");
}
echo "Tag erfolgreich geschrieben! \n";
?>
Listing 4: <?php
require_once 'MP3/Id.php';
// Instanz erzeugen und Datei lesen
$id3 = &new MP3_Id();
$err = $id3->read('../data/Little-Big-Man.mp3');
if (PEAR::isError($err)) {
die($err->getMessage() . "\n");
}
// Tag entfernen
$result = $id3->remove();
if (PEAR::isError($result)) {
die($result->getMessage() . "\n");
}
echo "Tag erfolgreich gelöscht! \n";
?>
PECL auch!
Seit dem Spätsommer 2004 gibt es die PHP-Extension ext/id3 [7], welche im Rahmen von PECL [6] entwickelt wird. Diese noch sehr junge Extension ist im Gegensatz zu MP3_Id nicht in PHP, sondern in C geschrieben und sollte damit um einiges schneller sein. Allerdings wird sie bislang nicht standardmäßig mit den PHP-Quellen ausgeliefert, da sie noch nicht als stable deklariert wurde. Die Funktionalitäten zum Lesen und Schreiben von ID3v1-Tags können allerdings bereits als stabil betrachtet werden.
Wenn Sie diese Extension verwenden wollen, können Sie sie entweder über den PEAR-Installer installieren oder sie auch fest in Ihre PHP-Installation einkompilieren. Wenn Sie mit einem Windows-System arbeiten, können Sie eine kompilierte DLL für die PHP-Version 5.0.x und 5.1.x von der PHP-Snapshot-Seite downloaden [9], die Sie in das Extensions-Verzeichnis Ihrer PHP-Installation (z.B. C:\php\ext) kopieren und über die php.ini einbinden können. Um die Extension nutzen zu können, müssen Sie PHP 4.3 oder höher installiert haben, da sie das Streams-API nutzt.
Haben Sie diese Hürde genommen, können Sie die Inhalte von ID3-Tags einlesen (Listing 5). Dazu verwendet man wie dargestellt die Funktion id3_get_tag(). Diese akzeptiert als ersten Parameter den Dateinamen oder die URL einer MP3-Datei. Alternativ können Sie eine Ressource, die Sie zuvor mit fopen() geöffnet haben, übergeben. Der zweite Parameter ist optional und bestimmt die Version des zu lesenden Tags. Im PHP-Manual [8] finden Sie wichtige Informationen zu den möglichen Konstanten und weiteren Funktionen. In Listing 6 sehen Sie, dass die Funktion die Inhalte des ID3-Tags als Array zurückgegeben hat.
Natürlich können Sie auch mit ext/id3 schreibend auf die Inhalte eines ID3-Tags einwirken. Dazu brauchen Sie nichts weiter, als ein Array, das dem in Listing 6 dargestellten ähnelt und die Funktion id3_set_tag(). Diese erhält als ersten Parameter wieder die Information, welche Datei verändert werden soll, als zweiten Parameter das Array mit den zu speichernden Informationen und als optionalen dritten Parameter eine Konstante für die zu schreibende ID3-Tag-Version. In der aktuellen Version kann id3_set_tag() nur Tags in den Versionen 1.0 und 1.1 schreiben, Listing 7 zeigt den dazu nötigen PHP-Code. Der Vollständigkeit halber zeigt Ihnen Listing 8, wie Sie mithilfe der Funktion id3_remove_tag() ein eventuell vorhandenes ID3-Tag entfernen können.
Ext/id3 verfügt noch über einige andere Funktionen, mit denen sich die Version des ID3-Tags in einer MP3-Datei bestimmen lässt (id3_get_version()) oder die id3_get_genre_*()-Funktionen, die beim Arbeiten mit Genrenamen und Genre-IDs helfen, da die in einem ID3-Tag gespeicherten Integer-Werte für ein Genre wenig aussagekräftig sind.
Listing 5: <?php
// Dateiname lokal
$tag1 = id3_get_tag('../data/Little-Big-Man.mp3', ID3_V1_1);
print_r($tag1);
// Dateiname als URL
// ACHTUNG: Entweder Sie haben DSL oder viel Zeit. ;-)
$tag2 = id3_get_tag('http://dirty-mac.com/sounds/little_big_man.mp3', ID3_V1_1);
print_r($tag2);
// Resource anstatt Dateiname
$fd = fopen('../data/Little-Big-Man.mp3', 'r');
$tag3 = id3_get_tag($fd, ID3_V1_1);
print_r($tag3);
?>
Listing 6: Array
(
[title] => Little Big Man
[artist] => Dirty Mac
[album] => Demo-Tape
[year] => 2001
[comment] => Song vom Demo-Tape
[track] => 5
[genre] => 17
)
Listing 7: <?php
$tag = array(
'title' => 'Neuer Titel',
'artist' => 'Andere Band',
'album' => 'Schlagertraum #3',
'year' => 1984,
'genre' => id3_get_genre_id('Rock'),
'comment' => 'Volksmusikal. Hochgenuss',
'track' => 5
);
// Tag schreiben
$result = id3_set_tag('../data/Little-Big-Man.mp3', $tag, ID3_V1_1 );
if ($result === false) {
echo "Tag nicht erfolgreich geschrieben! \n";
}
echo "Tag erfolgreich geschrieben! \n";
?>
Die zweite Generation
Obwohl man mithilfe von ID3v1-Tags bereits einige der wichtigsten Meta-Informationen über den Inhalt einer MP3-Datei spezifizieren kann, kommen die Einschränkungen der Versionen 1.0 und 1.1 von ID3-Tags deutlich zum Tragen:
- eingeschränkter Platz für Informationen, aufgrund fester Tag-Größe
- eingeschränkte Anzahl speicherbarer Attribute
Wie Sie sehen, sind ID3v1-Tags nicht erweiterbar, und das Speichern von Informationen über Titel, Interpret, Album oder Kommentar, die eine Länge von 30 Zeichen überschreiten, ist nicht möglich. Sollten Sie einen Titel wie The Hitchhiker's Guide to the Galaxy speichern wollen, wird davon in einem ID3v1-Tag nur noch The Hitchhiker's Guide to the übrig bleiben. Das Gleiche gilt für die Genre-Information. Da nur 1 Byte zur Speicherung zur Verfügung steht, ist man auf maximal 256 Genres beschränkt. Das mag sicherlich genug sein, aber wer weiß schon, was uns in Zukunft noch für Musikrichtungen erwarten?
Um die genannten Einschränkungen aufzuheben, wurden die ID3-Tags in der Version 2 [2], kurz ID3v2, entwickelt. ID3v2-Tags befinden sich am Anfang der Datei, noch vor den eigentlichen Audiodaten. Die Informationen sind in einzelnen Einheiten enthalten, welche man als Frames bezeichnet. Das ID3v2-Tag ist ein Container-Format, das heißt, es ist möglich im Laufe der Entwicklung von ID3 neue Frames hinzuzufügen. Hinzu kommt, dass ID3v2 bereits standardmäßig wesentlich mehr Informationen unterstützt als ID3v1. So sind Informationen über Copyrights, Beats per Minute (BPM) oder sogar ganze Liedtexte und Bilder speicherbar. Zusätzlich kann man ein ID3v2-Tag nach Belieben mit eigenen Frames erweitern. Hier die wichtigsten Vorteile auf einen Blick:
- keine Einschränkung in der Länge von Informationen
- flexibel um neue Frames erweiterbar
- Möglichkeit der Komprimierung von Tag-Inhalten
- unterstützt Unicode
- können Binärdaten wie Bilder oder andere Dateien enthalten
Aufgrund dieser erweiterten Möglichkeiten ist ein ID3v2-Tag um einiges schwieriger auszulesen als ein ID3v1-Tag. Die gute Nachricht ist, dass ext/id3 bereits in der Lage ist, die wichtigsten Informationen zu lesen. Wenn Sie den Code in Listing 9 ausführen, dann werden Sie die Ausgabe in Listing 10 erhalten und feststellen, dass mehr Informationen zurückgegeben wurden, als dies bei den Listings 5 bzw. 6 der Fall war.
Jeder Frame eines ID3v2-Tags besitzt eine eindeutige ID. So steht TOLY' beispielsweise für Original Lyricist'. Ext/id3 bietet zwei Funktionen an, um herauszufinden, worum es sich bei einem Frame genau handelt. Diese sind id3_get_frame_short_name() und id3_get_frame_long_name(). Sie erwarten als Parameter jeweils eine Frame-Id und liefern dann die entsprechende Kurzform oder Beschreibung des Frames. Die Kurzform verwendet ext/id3 übrigens als Array-Keys in seinen Ergebnis-Arrays.
In den kommenden Releases wird ext/id3 weitere nützliche Funktionen bieten und in der Lage sein, sämtliche in der ID3-Spezifikation beschriebenen Frames zu lesen und zu schreiben.
Listing 8: <?php
// Tag entfernen
$result = id3_remove_tag('../data/Little-Big-Man.mp3');
if ($result === false) {
echo "Tag nicht entfernt! \n";
}
echo "Tag erfolgreich entfernt! \n";
?>
Listing 9:
<?php
// ID3v2 Tag lesen
$tag = id3_get_tag('../data/Little-Big-Man.mp3', ID3_V2_3);
print_r($tag);
?>
Noch mehr Informationen
Bevor Sie jetzt ganz groß in das Geschäft des Online-Musikhandels einsteigen, lassen Sie uns Ihnen noch ein tolles Feature von MP3_Id zeigen. Man kann nämlich nicht nur Daten aus den ID3-Tags auslesen, auch die MP3-Datei an sich bietet einiges an interessanten Informationen. Sie verfügt nämlich über eine Bitrate, Spieldauer und weitere Eigenschaften. Diese kann man durch den Aufruf der Methode study() einlesen und dann wie bereits gesehen mithilfe von getTag() darauf zugreifen. Wie das geht, demonstriert Listing 12, und was dabei heraus kommt, sehen Sie in Listing 13. Leider ist nicht besonders ausführlich dokumentiert, auf welche Attribute man mit getTag() bzw. setTag() Zugriff hat. Im Zweifelsfall hilft ein Blick in die Datei MP3/Id.php im PEAR-Verzeichnis.
Listing 10: Array
(
[copyright] => Dirty Mac
[originalArtist] => Dirty Mac
[composer] => Marcus G?tze
[artist] => Dirty Mac
[title] => Little Big Man
[album] => Demo-Tape
[track] => 5/12
[genre] => (17)Rock
[year] => 2001
)
Listing 11: <?php
// Id eines ID3v2-Frames
$frame = 'TOLY';
$short = id3_get_frame_short_name($frame);
$descr = id3_get_frame_long_name($frame);
echo "Frame: $frame \n";
echo "Kurzform: $short \n";
echo "Beschreibung: $descr \n";
?>
Listing 12: <?php
require_once 'MP3/Id.php';
// Instanz erzeugen und Datei lesen
$id3 = &new MP3_Id();
$result = $id3->read('../data/Little-Big-Man.mp3');
// Fehler "Tag not found" wird ignoriert
if (PEAR::isError($result) && $result->getCode() !== PEAR_MP3_ID_TNF) {
die($result->getMessage() . "\n");
}
$result = $id3->study();
if (PEAR::isError($result)) {
die($result->getMessage() . "\n");
}
echo 'MPEG ' . $id3->getTag('mpeg_ver') . ' Layer ' . $id3->getTag('layer') . "\n";
echo $id3->getTag('mode') . "\n";
echo 'Dateigröße: ' . $id3->getTag('filesize') . " Bytes \n";
echo 'Bitrate: ' . $id3->getTag('bitrate') . "kB/s \n";
echo 'Spieldauer: ' . $id3->getTag('length') . " min \n";
echo 'Samplerate: ' . $id3->getTag('frequency') . "Hz \n";
?>
Listing 13: MPEG 1 Layer 3
Joint Stereo
Dateigröße: 4089856 Bytes
Bitrate: 128kB/s
Spieldauer: 04:15 min
Samplerate: 44100Hz
Fazit
Sie haben in diesem Artikel gesehen, welche Möglichkeiten es gibt, um mit PHP Informationen aus MP3-Dateien zu extrahieren. Sowohl die native id3-Extension als auch das PEAR-Package MP3_Id sind einfach anzuwenden und bieten bereits alle benötigten Funktionen. Ob Sie sich nun für die C-Extension oder die PHP-Implementierung entscheiden, bleibt letztlich Ihnen und Ihrer Hosting-Situation überlassen.
Carsten Lucke studiert Informatik an der Fachhochschule Brandenburg und entwickelt zusammen mit Stephan Schmidt (schst@php.net) die id3-Extension. Sie erreichen ihn per Mail (luckec@php.net) oder über seine Webseite (www.tool-garage.de). Stephan Schmidt ist Web Application Developer bei der 1&1 Internet AG sowie aktiver PEAR- und pecl-Entwickler. Sie erreichen ihn über schst@php.net.Links und Literatur[1] MusicLoad:
www.musicload.de[2] ID3-Standard:
www.id3.org[3] PEAR::MP3_Id:
pear.php.net/package/MP3_ID[4] PHP Extension and Application Repository (PEAR):
pear.php.net[5] Stephan Schmidt: Einführung in PEAR, in PHP Magazin 1.03
[6] PHP Extension and Community Library (PECL):
http://pecl.php.net[7] ID3-Erweiterung für PHP - ext/id:
pecl.php.net/package/id3[8] Dokumentation der id3 Funktionen:
www.php.net/manual/de/ref.id3.php[9] PHP Snapshots:
snaps.php.net/win32/PECL_UNSTABLE/