[PHP] Bilddaten von einer URL abzuholen und lokal zu speichern

[PHP] Bilddaten von einer URL abzuholen und lokal zu speichern

Bitte beim Beispiel unten die Abhängigkeiten zu meinen anderen Ressourcen berücksichtigen. Der SimpleLogger und der Errorhandler kommen zum Einsatz.

Zu erst die Klasse Fetch.php
PHP:
<?php
class CheckRemoteException extends Exception{};
class FetchException extends Exception{};
class FileNotExistsException extends Exception{};
class WriteLocalFileException extends Exception{};
class ReadLocalFileException extends Exception{};
class InvalidFileDataException extends Exception{};
  
  // Needed by DateTime class
date_default_timezone_set ( 'Europe/Berlin' );

/**
 * Fetch data from remote, store it locally and shrink the image data
 *
 * @author saftmeister
 */
class Fetch
{
  /**
   * The url where the data exists to retrieve
   *
   * @var string
   */
  private $url;
  
  /**
   * The percentage to shrink the image down to
   *
   * @var number
   */
  private $shrinkTo;
  
  /**
   * The locale file name
   *
   * @var Optional, if not given, the file name from
   *      remote will be taken
   */
  private $imageFileName;
  
  /**
   * Flag which indicates to retrieve the remote data
   *
   * @var boolean
   */
  private $needToFetch;
  
  /**
   * Flag which indicates to shrink the local image
   *
   * @var boolean
   */
  private $needToShrink;
  
  /**
   * Number of seconds, the local image can stay unmodified
   *
   * @var number
   */
  private $localMaxAge;
  
  /**
   * Path where to store the archive images
   *
   * @var string
   */
  private $archivePath;
  
  /**
   * Create a new Fetch instance
   *
   * @param string $url          
   * @param number|array $shrinkTo
   *          Either percentage as number or fixed dimensions as array('w' => number,'h' => number)
   * @param string $imageFileName          
   * @param number $maxAge
   *          The number of seconds when the local image expires
   * @param string $archivePath
   *          The path where to store archive images
   */
  public function __construct($url, $shrinkTo, $imageFileName = null, $maxAge = 0, $archivePath = null)
  {
    if (! function_exists ( 'curl_init' ))
    {
      throw new FetchException ( "Die curl-Erweiterung ist nicht geladen. Bitte php.ini entsprechend konfigurieren." );
    }
    
    if (! function_exists ( 'imagecreatefromjpeg' ))
    {
      throw new FetchException ( "Die GD2-Erweiterung nicht nicht geladen. Bitte php.ini entsprechend konfigurieren." );
    }
    
    clearstatcache ();
    
    $this->needToFetch = true;
    $this->needToShrink = true;
    
    $this->url = $url;
    $this->shrinkTo = $shrinkTo;
    $this->imageFileName = $imageFileName;
    $this->localMaxAge = $maxAge;
    $this->archivePath = $archivePath;
    
    if ($this->imageFileName == null)
    {
      $parts = parse_url ( $this->url );
      if ($parts)
      {
        $this->imageFileName = basename ( $parts ['path'] );
      }
    }
    
    if (file_exists ( $this->imageFileName ))
    {
      $this->needToShrink = false;
    }
  }
  
  /**
   * Check whether the remote file is newer than the local one
   *
   * @throws CheckRemoteException
   * @return boolean
   */
  public function checkIsNew()
  {
    if (! file_exists ( $this->imageFileName ))
    {
      return $this->needToFetch = true;
    }
    
    $current = new DateTime ();
    $localDate = new DateTime ();
    $localDate->setTimestamp ( filemtime ( $this->imageFileName ) ); // mtime
    
    if ($this->localMaxAge > 0)
    {
      $expires = clone $localDate;
      $expires->setTimestamp ( $expires->getTimestamp () + $this->localMaxAge );
      
      if ($expires->getTimestamp () < $current->getTimestamp ())
      {
        return $this->needToFetch = true;
      }
      return $this->needToFetch = false;
    }
    else
    {
      $response = get_headers ( $this->url, 1 );
      
      if (! $response)
      {
        throw new CheckRemoteException ( 'Lesen der Eigenschaften fehlgeschlagen' );
      }
      
      if ($response [0] != 'HTTP/1.1 200 OK')
      {
        throw new CheckRemoteException ( sprintf ( 'Server gab ungültige Antwort "%s" zurück', $response [0] ) );
      }
      
      if (isset ( $response ['Last-Modified'] ))
      {
        $remoteDate = new DateTime ( $response ['Last-Modified'] );
        
        if ($localDate->getTimestamp () < $remoteDate->getTimestamp ())
        {
          return $this->needToFetch = true;
        }
      }
      else if (isset ( $response ['Expires'] ))
      {
        $remoteDate = new DateTime ( $response ['Expires'] );
        
        if ($localDate->getTimestamp () > $remoteDate->getTimestamp ())
        {
          return $this->needToFetch = true;
        }
      }
    }
    
    return $this->needToFetch = false;
  }
  
  /**
   * Performs the file archivation
   *
   * @throws WriteLocalFileException
   */
  private function archive()
  {
    if ($this->archivePath != null)
    {
      if (is_dir ( $this->archivePath ))
      {
        $mtime = filemtime ( $this->imageFileName );
        $pinfo = pathinfo ( $this->imageFileName );
        
        $mDateTime = new DateTime ();
        $mDateTime->setTimestamp ( $mtime );
        $newName = sprintf ( "%s/%s-%s.%s", $this->archivePath, $pinfo ['filename'], $mDateTime->format ( "YmdHis" ), $pinfo ['extension'] );
        
        if (! copy ( $this->imageFileName, $newName ))
        {
          throw new WriteLocalFileException ( "Konnte lokale Datei nicht archivieren" );
        }
      }
      else
      {
        throw new WriteLocalFileException ( "Konnte lokale Datei nicht archivieren, Archiv-Pfad ist kein Verzeichnis" );
      }
    }
  }
  
  /**
   * Retrieve the remote file and store it locally
   *
   * @throws FetchException
   * @throws WriteLocalFileException
   */
  public function retrieve()
  {
    if (! $this->needToFetch)
    {
      return;
    }
    
    if (file_exists ( $this->imageFileName ))
    {
      $this->archive ();
    }
    
    $ch = curl_init ( $this->url );
    if (! curl_errno ( $ch ))
    {
      curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, true );
      curl_setopt ( $ch, CURLOPT_HEADER, false );
      $imageData = curl_exec ( $ch );
      if (curl_errno ( $ch ))
      {
        $error = curl_error ( $ch );
        curl_close ( $ch );
        throw new FetchException ( "Konnte Daten nicht empfangen: " . $error );
      }
      curl_close ( $ch );
      
      if (substr ( $imageData, 0, 3 ) != "\xFF\xD8\xFF")
      {
        throw new InvalidFileDataException ( "Die abgeholten Daten sind keine JPEG-Bild-Daten" );
      }
      
      $imageDataLen = strlen ( $imageData );
      
      $fd = fopen ( $this->imageFileName, "wb" );
      if ($fd)
      {
        if (fwrite ( $fd, $imageData, $imageDataLen ) != $imageDataLen)
        {
          fclose ( $fd );
          throw new WriteLocalFileException ( "Konnte Daten nicht in lokale Datei schreiben" );
        }
        fflush ( $fd );
        fclose ( $fd );
      }
      else
      {
        throw new WriteLocalFileException ( "Konnte lokale Datei nicht öffnen" );
      }
    }
    else
    {
      $error = curl_error ( $ch );
      throw new FetchException ( "Konnte keine Verbindung initiieren: " . $error );
    }
    $this->needToFetch = false;
    $this->needToShrink = true;
  }
  
  /**
   * Shrinks the image to the particular size given by percentage
   *
   * @throws InvalidArgumentException
   * @throws FileNotExistsException
   * @throws InvalidFileDataException
   * @throws WriteLocalFileException
   */
  public function shrink()
  {
    if (! $this->needToShrink)
    {
      return;
    }
    if ($this->needToFetch)
    {
      throw new FetchException ( "Bitte erst entfernte Datei abholen" );
    }
    
    if (is_int ( $this->shrinkTo ))
    {
      if ($this->shrinkTo < 1 || $this->shrinkTo >= 100)
      {
        throw new InvalidArgumentException ( "Die Schrumpfgröße ist ungültig (0 < erwartet < 100)" );
      }
    }
    else if (is_array ( $this->shrinkTo ))
    {
      if (! isset ( $this->shrinkTo ['w'] ) || intval ( $this->shrinkTo ['w'] ) < 1 || intval ( $this->shrinkTo ['w'] ) > 6000)
      {
        throw new InvalidArgumentException ( "Die Breitenabmessungsangabe zum Schrumpfen ist ungültig" );
      }
      if (! isset ( $this->shrinkTo ['h'] ) || intval ( $this->shrinkTo ['h'] ) < 1 || intval ( $this->shrinkTo ['h'] ) > 5000)
      {
        throw new InvalidArgumentException ( "Die Breitenabmessungsangabe zum Schrumpfen ist ungültig" );
      }
    }
    
    if (! file_exists ( $this->imageFileName ))
    {
      throw new FileNotExistsException ( "Die lokale Datei existiert nicht" );
    }
    
    $gdInfo = getimagesize ( $this->imageFileName );
    if (! $gdInfo)
    {
      throw new InvalidFileDataException ( "Konnte Dimensionen der Datei nicht lesen" );
    }
    
    $width = intval ( $gdInfo [0] );
    $height = intval ( $gdInfo [1] );
    
    if ($width < 1 || $width > 6000 || $height < 1 || $height > 5000)
    {
      throw new InvalidFileDataException ( "Dimensionen der Remote-Datei sind ungültig (w = " . $width . ", h = " . $height . ")!" );
    }
    
    if (is_int ( $this->shrinkTo ))
    {
      $newWidth = intval ( $width / 100 * $this->shrinkTo );
      $newHeight = intval ( $height / 100 * $this->shrinkTo );
    }
    else
    {
      $newWidth = intval ( $this->shrinkTo ['w'] );
      $newHeight = intval ( $this->shrinkTo ['h'] );
    }
    
    if ($newWidth < 1 || $newWidth > 6000 || $newHeight < 1 || $newHeight > 5000)
    {
      throw new InvalidFileDataException ( "Dimensionen der lokalen Datei sind ungültig (w = " . $newWidth . ", h = " . $newHeight . ")!" );
    }
    
    $imgJpg = imagecreatefromjpeg ( $this->imageFileName );
    if (! $imgJpg)
    {
      throw new InvalidFileDataException ( "Konnte Bilddaten nicht lesen" );
    }
    
    $imgNew = imagecreatetruecolor ( $newWidth, $newHeight );
    if (! $imgNew)
    {
      imagedestroy ( $imgJpg );
      throw new InvalidFileDataException ( "Konnte Puffer für Zieldatei nicht anfordern" );
    }
    
    if (! imagecopyresized ( $imgNew, $imgJpg, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height ))
    {
      imagedestroy ( $imgJpg );
      imagedestroy ( $imgNew );
      throw new InvalidFileDataException ( "Konnte Daten nicht kopieren" );
    }
    
    if (! imagejpeg ( $imgNew, $this->imageFileName ))
    {
      imagedestroy ( $imgJpg );
      imagedestroy ( $imgNew );
      throw new WriteLocalFileException ( "Konnte verkleinerte Daten nicht schreiben" );
    }
    
    imagedestroy ( $imgJpg );
    imagedestroy ( $imgNew );
    
    $this->needToShrink = false;
  }
  
  /**
   * Send image data to client
   *
   * @throws ReadLocalFileException
   * @throws FileNotExistsException
   */
  public function sendToClient()
  {
    if (file_exists ( $this->imageFileName ))
    {
      $data = file_get_contents ( $this->imageFileName );
      if (! $data)
      {
        throw new ReadLocalFileException ( "Konnte lokale Datei nicht zum lesen öffnen" );
      }
      
      $localDate = new DateTime ( 'UTC' );
      $localDate->setTimestamp ( filemtime ( $this->imageFileName ) );
      
      header ( 'Content-Type: image/jpeg' );
      header ( 'Content-Length: ' . strlen ( $data ) );
      header ( 'Last-Modified: ' . $localDate->format ( 'D, d M Y H:i:s \G\M\T' ) );
      header ( 'Cache-Control: public' );
      header ( 'ETag: "' . md5 ( $localDate->getTimestamp () . $this->imageFileName ) . '"' );
      
      echo $data;
    }
    else
    {
      throw new FileNotExistsException ( "Lokale Datei existiert nicht" );
    }
  }
  
  /**
   * Removes the local file (if it exists)
   */
  public function removeLocalFile()
  {
    if (file_exists ( $this->imageFileName ))
    {
      unlink ( $this->imageFileName );
    }
  }
}
Und eine Beispiel-Anwendung:

PHP:
<?php
require 'ErrorHandler.php';
require 'SimpleLogger.php';
require 'Fetch.php';

try 
{
  // Parameter: 
  // 1. URL - Bild-Adresse einer Webcam
  // 2. entweder Angabe in Prozent oder assoziatives Array mit w für Breite und h für Höhe, Angabe in Pixeln.
  // 3. Name für die lokale (auf dem lokalen Server) vorgehaltene Bild-Datei, wenn leer wird Name der URL verwendet.
  // 4. Anzahl der Sekunden, für die das Bild lokal vorgehalten werden soll. Wenn 0, wird zuerst Last-Modified und danach Expires-Header geprüft.
  // 5. Speicherort (Verzeichnis) für Archivierung. Archivierte Bilder bekommen den Zeitstempel in den Dateinamen, wann sie erstellt wurden.
  $fetcher = new Fetch('http://www.tegeler-segel-club.de/webcam/tsc_webcam.jpg', array('w' => 500, 'h' => 400), null, 60, '.');
  
  // Prüfen, ob neue Daten auf dem Remote-Server vorhanden sind
  $fetcher->checkIsNew();
  // neue Daten ggf. abholen und abhängig von Parameter 5 archivieren oder nicht
  $fetcher->retrieve();
  // Bild-Daten verkleinern oder vergrößern (möglich durch Dimensionierung)
  $fetcher->shrink();
  // Fertiges Bild an den Browser senden 
  $fetcher->sendToClient();
  // Ggf. lokales Bild wieder löschen - sollte nicht verwendet werden, um Bandbreite zu sparen.
  //$fetcher->removeLocalFile();
}
catch(Exception $ex)
{
  SimpleLogger::logException($ex);
  header('HTTP/1.1 500 Internal Server Error');
}
Autor
saftmeister
First release
Last update
Bewertung
0,00 Stern(e) 0 Bewertungen

More resources from saftmeister