EXIF-Daten mit PHP auslesen

Lesedauer: ~6 Minuten

Neben der IT ist ein weiteres meiner Hobbys die Fotografie. So arbeite ich nebenbei auch an einer kleinen Website zum Thema Fotografie und war hier vor kurzem mit dem Problem konfrontiert, dass ich EXIF-Daten aus den Fotos auslesen musste. Die größte Problematik, die sich hierbei gestellt hat, wird gut auf einer der Seiten des PHP-Handbuchs zur Funktion exif_read_data() dargestellt:

Leider hat jeder Kamerahersteller eine andere Vorstellung davon, wie man die Bilder beschreibt. Man kann sich also nicht darauf verlassen, das ein bestimmter Exif-Header vorhanden ist.

Daher habe ich mich beim Schreiben meiner Funktion hauptsächlich darum gekümmert, dass die beiden größten Hersteller, Canon und Nikon, unterstützt werden. Bei abschließenden Tests stellte sich heraus, dass die Funktion aber auch für Kameras der Hersteller Panasonic und Fujifilm mit kleinen Abstrichen geeignet ist. Die für mich relevanten Informationen aus den EXIF-Daten sind das Kameramodell, die Belichtungszeit, die Blende, die Brennweite und der ISO-Wert. Diese Informationen kann man in den EXIF-Daten unter IDF0 bzw. EXIF finden.

Auslesen kann man die Daten in PHP relativ einfach. Hierfür bietet sich der bereits genannte Befehl exif_read_data() an. Bei der ersten Betrachtung der Ausgabe der Funktion wird man feststellen, dass es hier zwei recht große Informationsblöcke gibt: IFD0 und EXIF. Hier soll uns vor allem der IDF0-Abschnitt dienlich sein. Den EXIF-Abschnitt werden wir nur nutzen, um an den ISO-Wert zu gelangen. An die gewünschten Daten kommt man recht schnell heran. Hierfür speichern wir uns erst einmal das Ergebnis von exif_read_data() in einer Variable.

$ifd0 = @exif_read_data($bild ,'IFD0' ,0);
$exif = @exif_read_data($bild ,'EXIF' ,0);

Nun können wir die von uns gewünschten Informationen auslesen.

//Kameramodell
$modell = $ifd0['Model'];

//Belichtungszeit
$belichtungszeit = $ifd0['ExposureTime'];

//Blende
$blende = $ifd0['COMPUTED']['ApertureFNumber'];

//Brennweite
$brennweite = $ifd0['FocalLength'];

//ISO-Wert
$iso = $exif['ISOSpeedRatings']

Die Werte die man hier nun erhält, sind zwar nun die richtigen, allerdings sind vor allem die Belichtungszeit und die Brennweite nicht in einem Format, wie man es aus Programmen wie Adobe Lightroom oder ähnliches gewohnt ist. Als normaler Nutzer tut man sich schwer diese zu lesen und daher habe ich noch weitere Funktionen genutzt, um diese Werte in das gewohnte Darstellungsformat zu formatieren. Dafür nutze ich die Funktionen getExposureTime und getFocalLength, wobei erstere Funktion für die Lesbarkeit einen Teil in eine kleine Hilfsfunktion ausgelagert hat. Hier wird allerdings nur ein Fließkommawert berechnet.

getExposureTime

function calcFloat($wert) {

    $pos = strpos($wert, '/');
    if ($pos === false)
        return (float) $wert;

    $a = (float) substr($wert, 0, $pos);
    $b = (float) substr($wert, $pos+1);

    return ($b == 0) ? ($a) : ($a / $b);

}

function getExposureTime($ifd0) {

    $apex    = calcFloat($ifd0);           
    $shutter = 1/$apex;

    if (round($shutter)==1)
        return '1';

    if ($shutter <= 1)
        return round($apex);

    return '1/' . round(1 / $apex);

}

getFocalLength

function getFocalLength($ifd0) {

    $bweite = explode("/", $ifd0);
    return ($bweite[0] / $bweite[1]);

}

Dies kann man nun mit dem vorherigen Codeschnipsel verbinden und bekommt so die gewohnten Werte.

//Belichtungszeit
$belichtungszeit = getExposureTime($ifd0['ExposureTime']);

//Brennweite
$brennweite = getFocalLength($ifd0['FocalLength']);

Letztendlich habe ich das Auslesen der EXIF-Daten in eine eigene Funktion ausgegliedert und diese etwas Nutzerfreundlicher gemacht. So wird am Ende ein Array mit den gefundenen Daten ausgegeben. Weiterhin wird geprüft ob überhaupt EXIF-Daten vorhanden sind und ob auch die gewünschten Einträge vorhanden sind. Nur die Informationen, für die auch Einträge vorhanden sind werden ausgelesen und in das Array eingetragen.

function calcFloat($wert) {
    $pos = strpos($wert, '/');
    if ($pos === false)
        return (float) $wert;

    $a = (float) substr($wert, 0, $pos);
    $b = (float) substr($wert, $pos+1);

    return ($b == 0) ? ($a) : ($a / $b);
}

function getExposureTime($ifd0) {
    $apex    = calcFloat($ifd0);           
    $shutter = 1/$apex;

    if (round($shutter)==1)
        return '1';

    if ($shutter <= 1)
        return round($apex);

    return '1/' . round(1 / $apex);
}

function getFocalLength($ifd0) {

    $bweite = explode("/", $ifd0);
    return ($bweite[0] / $bweite[1]);
}

function getExif($bild) {
    $bild_info = array(
        'modell'     => "",
        'belichtungszeit'  => "",
        'blende'  => "",
        'brennweite'  => "",
        'iso'       => ""
    );

    if ((isset($bild)) AND (file_exists($bild))) {
        $ifd0 = @exif_read_data($bild ,'IFD0' ,0);
        $exif = @exif_read_data($bild ,'EXIF' ,0);

        if (($ifd0 !== false) AND ($exif !== false)) {
            if (@array_key_exists('Model', $ifd0)) {
                $bild_info['modell'] = $ifd0['Model'];
            }

            if (@array_key_exists('ExposureTime', $ifd0)) {
                $bild_info['belichtungszeit'] = getExposureTime($ifd0['ExposureTime']);
            }

            if (@array_key_exists('ApertureFNumber', $ifd0['COMPUTED'])) {
                $bild_info['blende'] = $ifd0['COMPUTED']['ApertureFNumber'];
            }

            if (@array_key_exists('FocalLength', $ifd0)) {
                $bild_info['brennweite'] = getFocalLength($ifd0['FocalLength']);
            }

            if (@array_key_exists('ISOSpeedRatings',$exif)) {
                $bild_info['iso'] = $exif['ISOSpeedRatings'];
            }
        }
    }

    return $bild_info;
}

Mit dem Aufruf getExif(„Pfad/Zum/Bild“) kann man nun, sofern vorhanden, die EXIF-Daten zu dem entsprechenden Bild erhalten. Hierauf kann man nun beliebig aufbauen. Auf meiner Fotografie-Website habe ich beispielsweise diese Funktion noch mit einer Datenbank verbunden. Dadurch muss nicht bei jedem Seitenaufruf für jedes Bilder die EXIF-Daten erneut ausgelesen werden. Stattdessen werden die Daten nur einmalig beim Hochladen ausgelesen und in der Datenbank gespeichert. Dies erlaubt es mir auch die Daten auf einfache Weise nachträglich zu editieren, falls es doch mal zu einem Fehler kommen sollte.

Getestet wurde dieses Skript mit Kameras der Marke Canon, Nikon, Panasonic und Fuji, sowie einer Drohne von DJI. Hier hat dies auch gut funktioniert und es gab keine Fehler beim Auslesen. Auch bei Kameras von Panasonic und Fujifilm hat das Skript funktioniert, allerdings gibt es bei Panasonic Probleme das Modell auszulesen. Im Gegensatz zu anderen Herstellen schreibt Panasonic nur die reine Modellbezeichnung in das Feld und nicht wie andere Hersteller den Herstellernamen und das Modell. Dies könnte man umgehen, wenn man bei Panasonic zusätzlich noch das Feld ifd0[‚Make‘] ausließt. Dies war für meinen Anwendungszweck allerdings nicht wichtig. Von Fujifilm habe ich nur Bilder von einem Kameramodell getestet. Dies lief problemlos.