Add Exif data back to Facebook images

September 4, 2016 8 By addshore

2019 Update: This script now exists with an easy to use GUI

2018 Update: I touched this script again in March 2019 moving it to Github and updating it with Docker support

I start this post not by talking about Facebook, but about Google Photos. Google now offers unlimited ‘high resolution’ images within its service where high resolution is defined as 16MP for an image and 1080p for video. Of course there is some compression here that some may argue against but photos and video can also be uploaded at original quality (exactly as captured) and the cost of space for these files is very reasonable. So, It looks like I have found a new home for my piles of photos and videos that I want to be able to look back at in 20 years!

Prior to Google Photos developments I stored a reasonable number of images on Facebook, and now I want to also add them all to Google Photos, but that is not as easy as I first thought. All of your Facebook data can easily be downloaded which includes all of your images and videos, but not exactly as they were when you uploaded them, as they have all of the exif data such as location and time stripped. This data is actually available in a html file which is served with each Facebook album. So, I wrote a terribly hacky script in PHP for Windows to extract that data and re add it to the files so that they can be bulk uploaded to Google Photos and take advantage of the timeline and location features.

The code can be found below (it looks horrible but works…)

<?php

// README: Set the path to the extracted facebook dump photos directory here
$directory = 'C:/Users/username/Downloads/facebook-username/photos';

// http://www.sno.phy.queensu.ca/~phil/exiftool/
// README: Download this and set the path here (of the renamed exe)
$tool = 'C:\Users\username\exiftool.exe';

////////////////////////////////////////////////
//     Do not touch anything below here...    // =]
////////////////////////////////////////////////

echo "Starting\n";

$albums = glob( $directory . '/*', GLOB_ONLYDIR );

foreach ( $albums as $album ) {
    echo "Running for album $album\n";
    $indexFile = $album . '/index.htm';
    $dom = DOMDocument::loadHTMLFile( $indexFile );
    $finder = new DomXPath( $dom );
    $blockNodes = $finder->query( "//*[contains(concat(' ', @class, ' '), ' block ')]" );
    foreach ( $blockNodes as $blockNode ) {
        $imageNode = $blockNode->firstChild;
        $imgSrc = $imageNode->getAttribute( 'src' );
        $imgSrcParts = explode( '/', $imgSrc );
        $imgSrc = array_pop( $imgSrcParts );
        $imgLocation = $album . '/' . $imgSrc;

        echo "Running for file $imgLocation\n";

        $details = array();
        $metaDiv = $blockNode->lastChild;
        $details['textContent'] = $metaDiv->firstChild->textContent;
        $metaTable = $metaDiv->childNodes->item( 1 );
        foreach ( $metaTable->childNodes as $rowNode ) {
            $details[$rowNode->firstChild->textContent] = $rowNode->lastChild->textContent;
        }

        $toChange = '';

        $toChange[] = '"-EXIF:ModifyDate=' . date_format( new DateTime(), 'Y:m:d G:i:s' ) . '"';

        if ( array_key_exists( 'Taken', $details ) ) {
            $toChange[] = '"-EXIF:DateTimeOriginal=' .
                date_format( new DateTime( "@" . $details['Taken'] ), 'Y:m:d G:i:s' ) .
                '"';
        } else {
            continue;
        }
        if ( array_key_exists( 'Camera Make', $details ) ) {
            $toChange[] = '"-EXIF:Make=' . $details['Camera Make'] . '"';
        }
        if ( array_key_exists( 'Camera Model', $details ) ) {
            $toChange[] = '"-EXIF:Model=' . $details['Camera Model'] . '"';
        }
// Doing this will cause odd rotations.... (as facebook has already rotated the image)...
//      if ( array_key_exists( 'Orientation', $details ) ) {
//          $toChange[] = '"-EXIF:Orientation=' . $details['Orientation'] . '"';
//      }
        if ( array_key_exists( 'Latitude', $details ) && array_key_exists( 'Longitude', $details ) ) {
            $toChange[] = '"-EXIF:GPSLatitude=' . $details['Latitude'] . '"';
            $toChange[] = '"-EXIF:GPSLongitude=' . $details['Longitude'] . '"';
            // Tool will look at the sign used for NSEW!
            $toChange[] = '"-EXIF:GPSLatitudeRef=' . $details['Latitude'] . '"';
            $toChange[] = '"-EXIF:GPSLongitudeRef=' . $details['Longitude'] . '"';
            $toChange[] = '"-EXIF:GPSAltitude=' . '0' . '"';
        }

        exec( $tool . ' ' . implode( ' ', $toChange ) . ' ' . $imgLocation );
    }
}

echo "Done!\n";

I would rewrite it but I have no need to (as it works). But when searching online for some code to do just this I came up short and thus thought I would post the rough idea and process for others to find, and perhaps improve on.