Ein Pressefoto verlässt das Archiv, wandert durch drei Redaktionen und landet schließlich auf einer Webseite. Der Name des Fotografen stand in der Datenbank des Archivs, und genau dort ist er auch geblieben. In der Datei selbst steht nichts.
Im Bild wandert der Datenstreifen in die Datei hinein. Damit reisen Bildunterschrift, Urheber und Rechte mit, unabhängig davon, wo die Datei landet.
Was PHP iptcembed() macht
PHP iptcembed() setzt einen IPTC-Datenblock in eine JPEG-Datei. IPTC ist der Standard, mit dem die Presseverbände redaktionelle Angaben in Bilddateien festlegen: Bildunterschrift, Urheber, Copyright, Schlagworte. Jedes ernstzunehmende Bildbearbeitungsprogramm liest diese Felder.
Eine Sache fällt sofort auf, und sie ist die Quelle der meisten Missverständnisse: Die Funktion schreibt die Datei nicht. Sie liefert den neuen Dateiinhalt als String zurück, und um das Speichern muss man sich selbst kümmern.
<?php
$block = iptcFeld(2, 80, 'Nico Schubert'); /* siehe unten */
$inhalt = iptcembed($block, 'pressefoto.jpg');
if ($inhalt !== false) {
file_put_contents('pressefoto.jpg', $inhalt);
}
Ohne das file_put_contents() passiert auf der Platte gar nichts. PHP iptcembed() liest die Vorlage, baut daraus eine neue Fassung im Speicher und gibt sie zurück. Die Datei bleibt unverändert, und der Aufruf sieht trotzdem erfolgreich aus.
Die Signatur im Original
Die englische Beschreibung nennt beide Eigenheiten der Funktion in einem Absatz:
iptcembed(string $iptc_data, string $filename, int $spool = 0): string|bool
/* iptc_data - the binary IPTC block, already assembled
filename - path to the jpeg file to read
spool - 0 returns the image as a string, 2 or higher
sends the image directly to standard output
return - if spool is less than 2, the JPEG file with the
embedded IPTC data is returned as a string,
otherwise true on success or false on failure.
The function embeds binary IPTC data into a JPEG image. The
image data itself is not re-encoded, so no quality is lost. */
Der letzte Satz ist eine gute Nachricht: Der Bildinhalt wird nicht neu berechnet. Ein JPEG verliert also keine Qualität, egal wie oft die Metadaten geändert werden.
Der Datenblock: warum ein einfacher Text nicht genügt
PHP iptcembed() erwartet im ersten Parameter keinen lesbaren Text, sondern einen fertig zusammengesetzten Binärblock. Genau daran scheitern die meisten ersten Versuche, denn die Funktion nimmt einen gewöhnlichen String klaglos entgegen und erzeugt eine Datei, in der kein Programm etwas findet.
Ein einzelner Datensatz besteht aus fünf Teilen: einem festen Kennzeichen, der Gruppennummer, der Feldnummer, der Länge des Wertes und dem Wert selbst.
| Teil | Größe | Bedeutung |
| Kennzeichen | 1 Byte | immer 0x1C, leitet jeden Datensatz ein |
| Gruppe | 1 Byte | 2 für redaktionelle Angaben, 1 für Verwaltung |
| Feld | 1 Byte | welche Angabe, etwa 120 für die Bildunterschrift |
| Länge | 2 Byte | Anzahl der folgenden Zeichen, große Bytefolge zuerst |
| Wert | variabel | der eigentliche Text |
Eine Hilfsfunktion für beliebige Felder
PHP iptcembed() nimmt den fertigen Block entgegen, das Zusammensetzen bleibt dem Aufrufer überlassen. Diese fünf Teile immer wieder von Hand zu bauen wäre mühsam und fehleranfällig. Eine kleine Funktion nimmt einem die Arbeit dauerhaft ab.
<?php
function iptcFeld(int $gruppe, int $feld, string $wert): string
{
$laenge = strlen($wert);
if ($laenge > 32767) {
throw new LengthException('Wert zu lang fuer das kurze Laengenformat');
}
return chr(0x1C)
. chr($gruppe)
. chr($feld)
. pack('n', $laenge) /* zwei Byte, grosse Bytefolge zuerst */
. $wert;
}
Das Format n in pack() ist der entscheidende Teil. Es erzeugt eine vorzeichenlose 16-Bit-Zahl in der Reihenfolge, die der Standard verlangt. Wer hier die falsche Reihenfolge wählt, macht den gesamten Block ungültig, und zwar stillschweigend.
flowchart TD
A[Datei lesen] --> B{APP13 vorhanden}
B -->|ja| C[iptcparse aufrufen]
B -->|nein| D[leerer Datensatz]
C --> E[Werte uebernehmen]
D --> E
E --> F[Felder setzen]
F --> G[Block zusammenbauen]
G --> H[iptcembed aufrufen]
H --> I[Ergebnis speichern]
Der linke Ast ist der wichtige. Wer ihn auslässt, ersetzt beim Speichern alle vorhandenen Angaben durch die eigenen.
Die wichtigsten Felder im Überblick
Welche Felder PHP iptcembed() letztlich in die Datei schreibt, bestimmt allein der übergebene Block. Die Gruppe 2 enthält die Angaben, um die es in der Praxis geht. Alle Feldnummern folgen dem IPTC-Standard und werden von gängiger Bildsoftware verstanden.
| Feld | Inhalt | Hinweis |
| 2:005 | Titel | kurzer Bezeichner, kein ganzer Satz |
| 2:025 | Schlagwort | darf mehrfach vorkommen, je Wort ein Datensatz |
| 2:055 | Erstellungsdatum | Format JJJJMMTT, ohne Trennzeichen |
| 2:080 | Urheber | der Name des Fotografen |
| 2:116 | Copyright | Rechtevermerk, oft mit Jahreszahl |
| 2:120 | Bildunterschrift | der beschreibende Fließtext |
<?php
$block = iptcFeld(2, 120, 'Sonnenaufgang ueber dem Wattenmeer');
$block .= iptcFeld(2, 80, 'Nico Schubert');
$block .= iptcFeld(2, 116, 'Copyright 2026 php-space.info');
$block .= iptcFeld(2, 55, '20260921');
/* Schlagworte: je Wort ein eigener Datensatz im selben Feld. */
foreach (['Nordsee', 'Sonnenaufgang', 'Landschaft'] as $wort) {
$block .= iptcFeld(2, 25, $wort);
}
file_put_contents('foto.jpg', iptcembed($block, 'foto.jpg'));
Umlaute richtig speichern
PHP iptcembed() reicht die Werte unveraendert durch, es kennt keinen Zeichensatz. Ohne besondere Kennzeichnung nehmen Bildprogramme an, dass die Werte in Latin-1 vorliegen. Ein deutscher Text mit Umlauten erscheint dann als Folge von Fragezeichen. Die Lösung ist ein einzelner Datensatz in Gruppe 1, der den Zeichensatz benennt.
<?php
/* Die Bytefolge 1B 25 47 kennzeichnet UTF-8.
Sie gehoert an den Anfang des Blocks, vor alle Werte. */
$block = iptcFeld(1, 90, chr(0x1B) . chr(0x25) . chr(0x47));
$block .= iptcFeld(2, 120, 'Frühstück an der Küste');
file_put_contents('foto.jpg', iptcembed($block, 'foto.jpg'));
Diese drei Bytes stammen aus einer älteren Norm zur Zeichensatzumschaltung. Sie sehen wie ein Fremdkörper aus und sind trotzdem der vorgesehene Weg. Wer sie weglässt, hat funktionierende Metadaten mit falschen Umlauten.
Vorhandene Daten erhalten statt überschreiben
PHP iptcembed() ersetzt den kompletten IPTC-Bereich der Datei. Wer nur ein Feld ändern will, muss die vorhandenen Angaben vorher auslesen und wieder mitschreiben. Der Weg dorthin führt über getimagesize(), das den Rohbereich als zweiten Parameter herausgibt.
<?php
function copyrightSetzen(string $datei, string $vermerk): bool
{
getimagesize($datei, $bereiche);
$vorhanden = [];
if (isset($bereiche['APP13'])) {
$vorhanden = iptcparse($bereiche['APP13']) ?: [];
}
$vorhanden['2#116'] = [$vermerk]; /* nur dieses Feld ersetzen */
$block = '';
foreach ($vorhanden as $schluessel => $werte) {
[$gruppe, $feld] = explode('#', $schluessel);
foreach ($werte as $wert) {
$block .= iptcFeld((int) $gruppe, (int) $feld, $wert);
}
}
return (bool) file_put_contents($datei, iptcembed($block, $datei));
}
Die Schlüssel aus iptcparse() tragen die Form 2#116, also Gruppe, Rautezeichen, Feldnummer. Der Wert ist immer ein Array, auch wenn nur ein Eintrag darin steht. Das ist die Stelle, an der Schlagworte mit mehreren Einträgen sichtbar werden.
Direkt ausliefern statt speichern
Der dritte Parameter ändert das Verhalten grundlegend. Ab dem Wert 2 gibt PHP iptcembed() das Ergebnis unmittelbar aus, statt es zurückzugeben. Für einen Download, bei dem Kaeuferdaten eingebettet werden, spart das den Umweg über eine temporäre Datei.
<?php
$block = iptcFeld(2, 116, 'Lizenziert fuer Kunde 4711');
header('Content-Type: image/jpeg');
header('Content-Disposition: attachment; filename="bild.jpg"');
iptcembed($block, 'lager/original.jpg', 2); /* gibt direkt aus */
exit;
Vor dem Aufruf darf keine Ausgabe erfolgt sein, auch keine Leerzeile hinter einem schließenden PHP-Tag. Sonst landet sie vor den Bilddaten, und der Browser zeigt statt des Bildes nur Zeichensalat.
Die Gegenprobe
Ob die Angaben tatsächlich in der Datei stehen, lässt sich ohne fremdes Programm prüfen. Derselbe Weg, der beim Erhalten der Altdaten dient, taugt auch als Nachweis.
<?php
getimagesize('foto.jpg', $bereiche);
if (!isset($bereiche['APP13'])) {
echo 'Kein IPTC-Bereich in der Datei';
exit;
}
foreach (iptcparse($bereiche['APP13']) as $schluessel => $werte) {
echo $schluessel, ': ', implode(', ', $werte), PHP_EOL;
}
// 2#080: Nico Schubert
// 2#116: Copyright 2026 php-space.info
// 2#025: Nordsee, Sonnenaufgang, Landschaft
Fehlt der Bereich APP13 vollständig, hat PHP iptcembed() entweder nichts geschrieben oder das Ergebnis wurde nie gespeichert. Kommen die Felder an, sind aber leer, stimmt in aller Regel die Längenangabe im Datensatz nicht.
Grenzen: nur JPEG, kein PNG, kein WebP
Die Funktion arbeitet ausschließlich mit JPEG-Dateien. PNG und WebP kennen keinen APP13-Bereich in dieser Form, und ein Aufruf mit einer solchen Datei liefert false. Wer Formate wandelt, verliert die Metadaten dabei zwangsläufig; das gilt auch für den Weg, den JPG in WebP mit PHP konvertieren beschreibt.
Ebenso wenig ersetzt IPTC die Datenbank. Die Angaben in der Datei sind eine Zugabe für alle, die sie später in die Hände bekommen, kein verlässlicher Speicher. Und noch eine Abgrenzung gehört dazu: EXIF beschreibt, wie ein Bild entstanden ist, also Kamera, Belichtung und Ausrichtung. IPTC beschreibt, worum es auf dem Bild geht und wem es gehört. Der EXIF-Bereich bleibt beim Einbetten unangetastet, wie das Tutorial zu PHP Bilder skalieren und Thumbnails erzeugen an der Aufnahmerichtung zeigt.
Fazit
PHP iptcembed() schreibt redaktionelle Angaben dauerhaft in eine JPEG-Datei, ohne die Bildqualität anzutasten. Zwei Punkte entscheiden darüber, ob es beim ersten Versuch klappt: Der erste Parameter ist ein Binärblock und kein Text, und der Rückgabewert ist der neue Dateiinhalt, den man selbst speichern muss.
Mit der kleinen Hilfsfunktion für einzelne Felder ist der Rest überschaubar. Wer bestehende Angaben behalten will, liest sie vorher mit iptcparse() aus. Und wer deutsche Texte schreibt, setzt die Zeichensatzkennung in Feld 1:090, sonst stimmen alle Felder außer den Umlauten.