Submit Your Article Forum Rules

Page 3 of 4 FirstFirst 1234 LastLast
Results 21 to 30 of 31

Thread: Extracting and displaying EXIF data with PHP

  1. #21
    Administrator weegillis's Avatar
    Join Date
    Oct 2003
    Posts
    5,785
    On another day... Let's get rid of the confines of a two column table and swap-in an elastic index page.

    If you have followed the link in my sig you will notice that things have evolved along these lines. I'm sporting an HTML5 doctype, after all, so may as well show it. Since this page was never linked in to the main style sheet, I've left the new sheet in-page for now. It will get separated, in due course.

  2. #22
    Administrator weegillis's Avatar
    Join Date
    Oct 2003
    Posts
    5,785
    This module, and the index page itself have changed...
    PHP Code:
    function getEXIF($dir) {
     global 
    $exif;
     if (
    is_dir($dir)) {
      if (
    $dh opendir($dir)) {
       
    $count 1;
       while ((
    $file readdir($dh)) !== false) {
        if (
    stristr($file'.jpg')) {
         
    $exif = @exif_read_data($file0true);
         
    $str " <div class=\"item\">\n  <div class=\"thum\"><a href=\"viewer.php?img=" strTrunc($file4) . "\"><img src=\"";
         
    $str .= (hasSection('THUMBNAIL')) ? "thumbnail.php?file=" $file "\"" $file "\" width=\"195\"";
         
    $str .= " alt=\"" strTrunc($file4) . "\"";
         
    $str .= (hasSection('COMMENT')) ? " title=\"" $exif['COMMENT'][0] . "\"" null;
         
    $str .= "></a></div>\n  <ul>\n";
         
    $str .= "   <li>File: <b>" $exif['FILE']['FileName'] . "</b></li>\n";
    //     $str .= "   <li>Timestamp : " . date("Y-m-d", $exif['FILE']['FileDateTime']) . "</li>\n"; // maintenance only
         
    $str .= "   <li>Date taken: " $exif['EXIF']['DateTimeOriginal'] . "</li>\n";
         
    $str .= "   <li>Dimensions: " $exif['COMPUTED']['Width'] . " x " $exif['COMPUTED']['Height'] . " </li>\n";
         
    $gps = (hasSection('GPS')) ? getGPS() : null;
         if (
    $gps != null) {
          
    $str .= "   <li>Latitude  : " $gps[0] . "&deg;</li>\n";
          
    $str .= "   <li>Longitude : " $gps[1] . "&deg;</li>\n";
          
    $str .= "   <li class=\"win\"><a href=\"http://www.wikimapia.org/#lat=" $gps[0] . "&amp;lon=" $gps[1] . "&amp;z=17\">Map Reference " $count++ . "</a></li>\n";
         }
         
    $str .= "  </ul>\n </div>\n";
         echo 
    $str;
        }
       }
       
    closedir($dh);
      }
     }

    index page with CSS...

    PHP Code:
    <?php require_once "library.php"?><!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Thumbnail page with EXIF data</title>
    <meta name="robots" content="noindex,nofollow">
    <style>
    body { font-size: 100%; text-align: center;}
    div#wrap {
     width: 98%;
     margin: 0 auto;
     overflow: auto;
    }
    div.item {
     float: left;
     width: auto;
     overflow: auto;
     text-align: left;
     font: normal 0.8em/1.0em Calibri, Arial, Helvetica, sans-serif;
    }
    div.thum {
     float: left;
     width: auto;
     height: auto;
     margin: 0 1em 1em 0;
    }
    .item ul {
     list-style: none;
     margin: 1em 0;
     padding: 0;
    }
    </style>
    </head>
    <body>
    <div id="wrap">
    <?php $imgDir "."getEXIF($imgDir); ?>
    </div>
    </body>
    </html>
    Last edited by weegillis; 06-13-2012 at 03:41 AM. Reason: Added in COMMENT in title attribute of thumbnail / commented out timestamp / @exif

  3. #23
    Administrator weegillis's Avatar
    Join Date
    Oct 2003
    Posts
    5,785
    This should allow smart phones to at least easily access and peruse the thumbnails. Since it's all server-side, they're not feeling the full hit of the bandwidth until they go to single frame view. As I said earlier, optimizing the images really pays off here. These frames, even as optimized as they are all cost money to view on a smart phone.

  4. #24
    Junior Member
    Join Date
    Mar 2010
    Posts
    15
    Hi weegillis. That will become handy when it's ready for production use. My compliments for a very informative and nicely written article.

    Sara
    Last edited by weegillis; 06-11-2012 at 08:36 PM. Reason: off-topic portion moved to another thread.

  5. #25
    Administrator weegillis's Avatar
    Join Date
    Oct 2003
    Posts
    5,785
    Quote Originally Posted by juto View Post
    That will become handy when it's ready for production use.
    While not there yet, I am using it in trial production on a live site, in it's revised form, of course.

    This version is not compatible with the earlier posted one, so out with the old, and in with the new. Drats! All that work...

    I'm still working at limiting the number of files needed in a folder to zero, but not there yet. We still need these three files in the respective jpeg folder.

    1. index.php
    2. viewer.php
    3. thumbnail.php (I know, this one has no excuse for still being here...)

    Having created templates for the two pages, the files are very small, and limited only to an editable title and a couple of requires. How easy is that? Edit a title and create not one page, but a whole folder of them. Headings are generated from the photo, separate from the HTML title tag.

    PHP Code:
    index.php

    <?php 
     
    // v0.4.120623
     
    $title "text plus a space "
     
    $imgDir ".";
     require_once 
    "[../path back to root]/includes/library.php"
     require_once 
    "[../path back to root]/templates/thumbs.php";
     
    ?>


    viewer.php

    <?php 
     
    // v0.4.120623
     
    $title "text plus a space ";
     require_once 
    "[../path back to root]/includes/library.php"
     require_once 
    "[../path back to root]/templates/viewer.php";
     
    ?>
    thumbnails.php you already have.

    The templates are very simple, and will be tacked on next. The library has undergone/is undergoing significant revision, ergo the need to scrap the old stuff. It will need a little explanation, but I won't repeat myself if it's in the earlier proof of concept above. We'll stick to the new stuff. There is a CSS component to this, some of which is derived from the very original seeds of this project. I'll tack it on last.
    Last edited by weegillis; 06-25-2012 at 04:16 AM. Reason: code edit

  6. #26
    Administrator weegillis's Avatar
    Join Date
    Oct 2003
    Posts
    5,785
    Here are the two template files. They are named .php but needn't be. They can actually have any extension that suits you.

    PHP Code:
    thumbs.php

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title><?php ins($title); ?> &bull; photo navigator</title>
    <meta name="robots" content="noindex,nofollow">
    <link href="/css/exif.css" rel="stylesheet">
    </head>
    <body>
    <div id="wrap">
    <?php getEXIF($imgDir); ?>
    </div>
    </body>
    </html>

    viewer.php

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title> <?php ins($title); ?>&bull; photo inspector</title>
    <meta name="robots" content="noindex,nofollow">
    <link rel="stylesheet" href="/css/exif.css">
    </head>
    <body>
    <?php 
     $image 
    = isset($_REQUEST['img']) ? $_REQUEST['img'] : null;
     
    readExif($image); 
     
    ?> 
    <div id="header"><div id="breadcrumb"><a href="./" title="Back to Photo Navigator">&lt;&lt;&lt; Thumbnails</a></div><div class="headerbox"><?php ins($tags['title']) ?></div></div>
    <div id="image"><div class="imagebox"><div class="imageshow"><?php getImg($image); ?></div></div></div>
    <div id="exif">
     <div class="exifbox"> 
      <div class="exifboxtable">  
       <table>
    <?php printData(); ?> 
       </table>   
      </div>  
     </div> 
    </div>
    </body>
    </html>
    thumbs.php is pretty straight up. We only have two variables, $title and $imgdir. In the viewer we have the $title variable, as well as the query string variable which is the foundation of the page, else it wouldn't exist. By the time page content is being generated a table of tags has already been populated with values; ergo, $tags['title'] in the 'headerbox' (yes, this will be history soon). Instead of passing a slough of variables to the output function, we're now only passing a simple table, as promised. It's in the global scope, though, so invisible to the templates, source code wise.

    library.php is next.

  7. #27
    Administrator weegillis's Avatar
    Join Date
    Oct 2003
    Posts
    5,785
    The library contains a derived part, which had to go through more change to allow for a single table, instead of the chorus of variables. I also moved the normalization into this method and removed the earlier function, as it is no longer needed. The revision I'm currently using is here:

    PHP Code:
    // adapted from www.quietless.com/kitchen/extract-exif-data-using-php-to-display-gps-tagged-images-in-google-maps/
    function toDecimal($deg$min$sec$hem) { $d =  round(($deg $min/60 $sec/3600), 7); return ($hem=='S' || $hem=='W') ? $d*=-$d;}
    function 
    divide($a) { $e explode('/'$a); return (!$e[0] || !$e[1]) ? $e[0] / $e[1]; }
    function 
    getGPS() {
     global 
    $exif$tags
     if (
    $exif) {
      
    $lat $exif['GPS']['GPSLatitude']; 
      
    $log $exif['GPS']['GPSLongitude'];
      if (!
    $lat || !$log) return null;
      
    $tags['latdeg'] = divide($lat[0]);
      
    $lat_min divide($lat[1]);
      
    $lat_min_frac $lat_min - (int)($lat_min);
      
    $lat_min = (int)($lat_min);
      
    $tags['latmin'] = $lat_min;
      
    $tags['latsec'] = round($lat_min_frac 602);
      
    $tags['lathem'] = $exif['GPS']['GPSLatitudeRef'];
      
    $tags['logdeg'] = divide($log[0]);
      
    $log_min divide($log[1]);
      
    $log_min_frac $log_min - (int)($log_min);
      
    $log_min = (int)($log_min);
      
    $tags['logmin'] = $log_min;
      
    $tags['logsec'] = round($log_min_frac 602);
      
    $tags['loghem'] = $exif['GPS']['GPSLongitudeRef'];
      
    $tags['latitude'] = toDecimal($tags['latdeg'], $tags['latmin'], $tags['latsec'],$tags['lathem']);
      
    $tags['longitude'] = toDecimal($tags['logdeg'], $tags['logmin'], $tags['logsec'], $tags['loghem']);
      return array(
    $tags['latitude'], $tags['longitude']);
     } else {
      return 
    null;
     }

    The math in the normalization method agrees with what Windows calculates, albeit I round to 7 decimal places for WikiMapia.

    At some point I plan to write in a fallback for missing GPS. The lat and long can be added via one of the editable tags, and the script could detect them, allowing for a WM map reference.

    I know this isn't Geotagging, but if you ask me, it's a lot more accurate, and not susceptible to the whims of a black hole giant. Truth be known, they soak it up, anyway. I've had lots of places that I marked in WM eventually show up on GE. And the detail cards on WM show up almost immediately in search.

    And what's more, the client has not the need to download a s---load of bulky script to see these place marks and detail cards. Still, it's ad driven, we cannot escape that. It all ends up in somebody's pail.

    But I digress. All we've got so far is the GPS data. Let's move on...
    Last edited by weegillis; 06-25-2012 at 04:55 AM.

  8. #28
    Administrator weegillis's Avatar
    Join Date
    Oct 2003
    Posts
    5,785
    We need a few utilities to help us the rest of the way, so I'll tack them here. Their purpose will reveal itself presently.

    PHP Code:

    function imgConfirm($arg) { $img1 $arg ".jpg"$img2 $arg ".JPG"; return file_exists($img1) ? $img1 : (file_exists($img2) ? $img2 null);};
    function 
    hasSection($arg) { global $exif; return stristr($exif['FILE']['SectionsFound'], $arg); }
    function 
    strTrunc($arg$trunc) { return substr($arg0strlen($arg)-$trunc); };
    function 
    filter($arg) { return preg_replace('/[^\p{L}\p{M}\p{Z}\p{N}\p{P}]/u'''$arg); };
    function 
    ins($arg) { echo $arg; } 
    As stated, purpose will reveal itself. We've already used ins() a couple of times to this point, and, hasSection() and strTrunc() are previously discussed. The rest will come out.

  9. #29
    Administrator weegillis's Avatar
    Join Date
    Oct 2003
    Posts
    5,785
    Almost forgot where we were here...

    The index page is just thumbnails, either extracted or derived from discovered images. The navigator hooks onto any jpg/JPG files that exist in the folder. If one is detected it will get a thumbnail, by hook or by crook. EXIF data is extracted and given back as available. Map links are created if GPS data is available. Socially, this system is not viable as any social media would strip GPS data on all photo uploads to its servers. We are dealing strictly with what an individual publisher wishes to do with GPS laden images on their own domain. One would always be well advised to consider the privacy of others.

    PHP Code:
    // thumbs
    function getEXIF($dir) {
     global 
    $exif;
     if (
    is_dir($dir)) {
      if (
    $dh opendir($dir)) {
       
    $count 1;
       while ((
    $file readdir($dh)) !== false) {
        if (
    stristr($file'.jpg')) {
         
    $exif = @exif_read_data($file0truetrue);
         
    $str " <div class=\"item\">\n  <div class=\"thum\"><a href=\"viewer.php?img=" strTrunc($file4) . "\"><img src=\"";
         
    $str .= hasSection('THUMBNAIL') ? "thumbnail.php?file=" $file "\"" $file "\" width=\"195\"";
         
    $sub filter($exif['IFD0']['Subject']);
         
    $alt $sub $sub strTrunc($file4);
         
    $str .= " alt=\"$alt\" title=\"";
         
    $com hasSection('COMMENT') ? $exif['COMMENT'][0] : null;
         
    $ttl filter($exif['IFD0']['Title']);
         
    $title $ttl $ttl $com;
         
    $str .= $title $title strTrunc($file4); 
         
    $str .= "\"></a></div>\n  <ul>\n";
         
    $str .= "   <li>File: <b>" $exif['FILE']['FileName'] . "</b></li>\n";
         
    $str .= $sub "   <li>Subject: $sub</li>\n" null;
         
    $str .= "   <li>Date taken: " strTrunc($exif['EXIF']['DateTimeOriginal'], 9) . "</li>\n";
         
    $str .= "   <li>Dimensions: " $exif['COMPUTED']['Width'] . " x " $exif['COMPUTED']['Height'] . " </li>\n";     
         
    $gps hasSection('GPS') ? getGPS() : null;
         if (
    $gps != null) {
          
    $str .= "   <li>Latitude  : " $gps[0] . "&deg;</li>\n";
          
    $str .= "   <li>Longitude : " $gps[1] . "&deg;</li>\n";
          
    $str .= "   <li class=\"win\"><a href=\"http://www.wikimapia.org/#lat=" $gps[0] . "&amp;lon=" $gps[1] . "&amp;z=17\" title=\"Off-site\">Map Reference " $count++ . "</a></li>\n";
         }     
         
    $str .= "  </ul>\n </div>\n";
         echo 
    $str;
        }
       }
       
    closedir($dh);
      }
     }

    The viewer describes slightly more than the navigator page as one would hope. The GPS, if available, is part of the output, with WM link, otherwise a report, "No GPS tags found". The GPS at this point is displayed in both sexagesimal and decimal formats, the latter being what is fed to WM. The heading is taken from the available tag data or truncated file name as a fallback. The caption for the image combines both the COMMENT and the IFD0 Comments sections, if they exist.
    Last edited by weegillis; 06-25-2012 at 03:04 PM.

  10. #30
    Administrator weegillis's Avatar
    Join Date
    Oct 2003
    Posts
    5,785
    These functions power the viewer. Each does what their names suggest: read the EXIF data and store available tags; display the image; print the available data.


    PHP Code:
    // viewer
    function getImg($img) {
     global 
    $exif$tags;
     
    $imgx imgConfirm($img);
     if (
    $imgx) { 
     
    $str "<img src=\"$imgx\" ";
     
    $chtm =$exif['COMPUTED']['html'];
     if (!
    $chtm) {
      
    $fil_wid $exif['COMPUTED']['Width'];
      
    $fil_hgt $exif['COMPUTED']['Height'];
      
    $str .= "width=\"" . (($fil_wid 0) ? $fil_wid "100%") . "\" height=\"" . (($fil_hgt 0) ? $fil_hgt "100%") . "\"";
     } else {
      
    $str .= $chtm;
     }
      
    $str .= " alt=\"" $tags['alt'] . "\" title=\"" $tags['title'] . "\">";
      echo 
    $str;
     } else {
      echo 
    "Image not found.";
     }
    }
    function 
    readExif($img) {
     global 
    $exif$use_com$errStr$tags;
     
    $errStr ""
     
    $imgx imgConfirm($img);
     if (
    $imgx) {
      if (@
    exif_read_data($imgx)) {
       
    $exif = @exif_read_data($imgx0truefalse);
       echo 
    "<div id=\"exifdump\">\n";
        
    print_r($exif);
       echo 
    "</div>\n";
       
    $comment hasSection('COMMENT') ? $exif['COMMENT'][0] : null;
       
    $alt filter($exif['IFD0']['Subject']);
       
    $title filter($exif['IFD0']['Title']);
       
    $comments filter($exif['IFD0']['Comments']);
       
    $author filter($exif['IFD0']['Author']);
       
    $alt$alt $alt strTrunc($imgx4);
       
    $title $title $title strTrunc($imgx4);
       
    $comments $comments $comments null;
       
    $author $author $author null;
       
    $tags = array(
        
    'alt'=> $alt,
        
    'title' => $title,
        
    'author' => $author,
        
    'comments' => $comments,
        
    'comment' => $comment
       
    );
       if (
    hasSection('GPS')) {
        
    $gps $exif['GPS']['GPSAltitudeRef'];
        
    $alt_ref = ($gps !== null) ? $gps null;
        
    $tags['altref'] = (int)($alt_ref);
        
    $gps $exif['GPS']['GPSAltitude'];
        
    $gps_alt = ($gps !== null) ? round(divide($gps), 4) : "N/A";
        
    $tags['altitude'] = $gps_alt;
        
    $gps getGPS();
        if (
    $gps != null) {
         
    $tags['latitude'] = $gps[0];
         
    $tags['longitude'] = $gps[1];
        }
       } else {
        
    $errStr .= "    <tr>\n<td colspan=\"3\"><p>No GPS tags found.<p></td>\n";
       }    
      } else {
       
    $errStr .= "    <tr>\n<td colspan=\"3\"><p>No EXIF tags found.</p></td>\n";
      }
     }
    }
    function 
    printData() {
     global 
    $errStr$exif$tags;
     
    $str ""
     if (
    $tags['comment']) { $str .= "    <tr>\n     <td colspan=\"3\">\n      <p class=\"comment\">" $tags['comment'] . "</p>\n     </td>\n    </tr>\n"; }
     if (
    $tags['comments']) { $str .= "    <tr>\n     <td colspan=\"3\">\n      <p class=\"comment\">" $tags['comments'] . "</p>\n     </td>\n    </tr>\n"; }
     if (!
    $errStr) {
      
    $str .= "    <tr><th scope=\"col\">LATITUDE</th><th scope=\"col\">LONGITUDE</th><th scope=\"col\">ALTITUDE</th></tr>\n    <tr>\n";
      
    $str .= "     <td>\n      <p>" $tags['latdeg'] . "&deg; " $tags['latmin'] . "&rsquo; " $tags['latsec'] . "&rdquo; " $tags['lathem'] . "</p>\n      <p>" $tags['latitude'] . "&deg;</p>\n     </td>\n";
      
    $str .= "     <td>\n      <p>" $tags['logdeg'] . "&deg; " $tags['logmin'] . "&rsquo; " $tags['logsec'] . "&rdquo; " $tags['loghem'] . "</p>\n      <p>" $tags['longitude'] . "&deg;</p>\n     </td>\n";    
      
    $h = isset($_REQUEST['h']) ? $_REQUEST['h'] : 0;
      
    $z = ((intval($h) <= && intval($h) >= -4) ? "&amp;z=" . (string)(17 intval($h)) : "&amp;z=17"); 
      
    $str .= "     <td>\n      <p>" . (($tags['altitude'] == "N/A") ? $tags['altitude'] : $tags['altitude'] . " m " . (($tags['altref']) ? "Below" "Above") . " Sea Level.") . "</p>\n      <p>Go to this <a href=\"http://www.wikimapia.org/#lat=" $tags['latitude'] . "&amp;lon=" $tags['longitude'] . $z "\">location on the map</a></p>\n     </td>\n";  
     } else {
      
    $str .= $errStr;
     }
     
    $str .= "    </tr>";
     echo 
    $str;
    }  
    // 
    Last edited by weegillis; 06-25-2012 at 03:11 PM.

Page 3 of 4 FirstFirst 1234 LastLast

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •