function flickr_photos_exif_read(&$photo)
{
    $map = flickr_photos_media_map();
    if ($map[$photo['media']] == 'video') {
        return not_okay("video does not contain EXIF data");
    }
    $fname = "{$photo['id']}_{$photo['originalsecret']}_o.{$photo['originalformat']}";
    $froot = $GLOBALS['cfg']['flickr_static_path'] . flickr_photos_id_to_path($photo['id']);
    $path = "{$froot}/{$fname}";
    if (!preg_match("/\\.jpe?g\$/i", $path)) {
        return not_okay("not a JPEG photo");
    }
    if (!file_exists($path)) {
        return not_okay("original photo not found");
    }
    if (!filesize($path)) {
        return not_okay("original photo is empty");
    }
    # TO DO: cache me?
    $exif = exif_read_data($path);
    if (!$exif) {
        return not_okay("failed to read EXIF data");
    }
    # TO DO: expand EXIF tag values
    $to_simplejoin = array('SubjectLocation', 'GPSLatitude', 'GPSLongitude', 'GPSTimeStamp');
    foreach ($to_simplejoin as $tag) {
        if (is_array($exif[$tag])) {
            $exif[$tag] = implode(",", $exif[$tag]);
        }
    }
    # TO DO: work out how/where individual EXIF tags get
    # "prettified" ...
    ksort($exif);
    return okay(array("rows" => $exif));
}
function flickr_photos_metadata_path(&$photo)
{
    $root = $GLOBALS['cfg']['flickr_static_path'];
    $path = flickr_photos_id_to_path($photo['id']) . "/";
    $fname = "{$photo['id']}_{$photo['originalsecret']}_i.json";
    $meta = $root . $path . $fname;
    return $meta;
}
function flickr_urls_photo_original(&$photo)
{
    if ($GLOBALS['cfg']['enable_feature_storage_s3']) {
        loadlib('storage_s3');
        return storage_s3_url_photo($photo, 'o');
    }
    $secret = $photo['originalsecret'];
    $sz = "o";
    $ext = $photo['originalformat'];
    $root = $GLOBALS['cfg']['flickr_static_url'];
    $path = flickr_photos_id_to_path($photo['id']);
    $fname = "{$photo['id']}_{$secret}_{$sz}.{$ext}";
    return $root . $path . "/" . $fname;
}
function flickr_photos_import_photo_files(&$photo, $more = array())
{
    if ($GLOBALS['cfg']['enable_feature_storage_s3']) {
        return flickr_photos_import_photo_files_s3($photo, $more);
    }
    $root = "http://farm{$photo['farm']}.static.flickr.com/{$photo['server']}/{$photo['id']}";
    $small = "{$root}_{$photo['secret']}_z.jpg";
    $ext = $photo["originalsecret"] ? $photo["originalformat"] : "jpg";
    if ($photo['originalsecret']) {
        # This is probably really only necessary for
        # Cal's account (20111208/straup)
        $orig = $ext ? "{$root}_{$photo['originalsecret']}_o.{$ext}" : null;
    } else {
        $orig = "{$root}_{$photo['secret']}_b.{$ext}";
    }
    if ($photo['media'] == 1) {
        # http://www.flickr.com/photos/straup/2378794972/play/site/3bfc8d2bb9/
        # http://www.flickr.com/photos/straup/2378794972/play/orig/5771b28b4b/
        $video = $photo['originalsecret'] ? "orig/{$photo['originalsecret']}" : "site/{$photo['secret']}";
        $orig = "http://www.flickr.com/photos/{$nsid}/{$photo['id']}/play/{$video}";
    }
    #
    $path = $GLOBALS['cfg']['flickr_static_path'] . flickr_photos_id_to_path($photo['id']);
    if (!file_exists($path)) {
        mkdir($path, 0755, true);
    }
    #
    $local_small = "{$path}/" . basename($small);
    $local_orig = "{$path}/" . basename($orig);
    $local_info = str_replace("_o.{$photo['originalformat']}", "_i.json", $local_orig);
    $local_comments = str_replace("_o.{$photo['originalformat']}", "_c.json", $local_orig);
    # god how I wished we had implemented a system to records and pass back
    # to the API *what* had actually changed when a photo was updated; for
    # now we'll just assume that the photo hasn't been rotated or replaced...
    # (2011115/straup)
    $req = array();
    if ($more['force'] || !file_exists($local_small)) {
        $req[] = array($small, $local_small);
    }
    if ($more['force'] || !file_exists($local_orig)) {
        # see above
        if ($orig) {
            $req[] = array($orig, $local_orig);
        }
    }
    # for now, just always fetch meta files because who knows
    # whether anything has changed; note the "json:foo:path"
    # syntax which are hints to tell the code to handle http_multi
    # responses (below) whether to inspect the contents of the
    # data returned by the flickr API
    if (!isset($more['skip_meta'])) {
        # basic photo info
        # viewer id and not photo owner?
        $flickr_user = flickr_users_get_by_user_id($photo['user_id']);
        $method = 'flickr.photos.getInfo';
        $args = array('auth_token' => $flickr_user['auth_token'], 'photo_id' => $photo['id']);
        list($url, $args) = flickr_api_call_build($method, $args);
        $api_call = $url . "?" . http_build_query($args);
        $req[] = array($api_call, "json:info:{$local_info}");
        # fetch comments, which is to say check to see if there
        # are any new photos worth storing
        $fetch_comments = 1;
        if ($more['min_date']) {
            $method = 'flickr.photos.comments.getList';
            $args = array('photo_id' => $photo['id'], 'min_comment_date' => $more['min_date']);
            $rsp = flickr_api_call($method, $args);
            if ($rsp['ok'] && !isset($rsp['rsp']['comments']['comment'])) {
                $fetch_comments = 0;
            }
        }
        if ($fetch_comments) {
            $method = 'flickr.photos.comments.getList';
            $args = array('photo_id' => $photo['id']);
            list($url, $args) = flickr_api_call_build($method, $args);
            $api_call = $url . "?" . http_build_query($args);
            $req[] = array($api_call, "json:comments:{$local_comments}");
        }
    }
    # now go!
    # fetch all the bits using http_multi()
    if ($count = count($req)) {
        _flickr_photos_import_fetch_multi($req);
    }
}