30 April 2013

Downloading a couple originals from a Shared Photostream is no big deal, but downloading all of them from a large photostream can quickly become cumbersome. Below is a Drush script that interacts with the JSON-driven photostream backend. There is no official API (at the time of writing), so I fully expect this to break at some point.

What is Drush?

Drush is the Drupal module that enables command-line access. I used Drush to gain access to the HTTP client in Drupal. The code could easily be adjusted to work with any other client of substance. Simply drop the script below into any module folder on a Drupal installation with Drush support, and you are ready to go.

The Script: photostream.drush.inc

<?php
/**
 * @file
 * Provide a drush command to interact with a photostream.
 */
/**
 * Configure the drush command.
 */
function photostream_drush_command() {
  $items['photostream-download'] = array(
    'description' =--> 'Download large versions of the images from a photostream.'
  );
  return $items;
}
/**
 * Download all photos from a photostream.
 * @param $stream_id string
 * @param $save_path string
 */
function drush_photostream_download($stream_id = '', $save_path = '') {
  if (empty($stream_id) || empty($save_path)) {
    drush_print(dt("Usage: photostream-download 'A45qXGF1Qtibd' '/tmp/path/to/export'"));
    return;
  }
  
  // Configure basic info for use across requests.
  $api = 'https://p04-sharedstreams.icloud.com';
  $method = 'POST';
  $retry = 1;
  $timeout = 5;
  $headers = array(
    'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language' => 'en-US,en;q=0.5',
    'Cache-Control' => 'no-cache',
    'Connection' => 'keep-alive',
    'Content-Type' => 'text/plain; charset=UTF-8',
    'Origin' => 'https://www.icloud.com',
    'Pragma' => 'no-cache',
    'Referer' => 'https://www.icloud.com/photostream/',
    'User-Agent' => 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:20.0) Gecko/20100101 Firefox/20.0 FirePHP/0.7.2',
    'x-insight' => 'activate',
  );
  
  // Get the list of items.
  $url = "$api/$stream_id/sharedstreams/webstream";
  $data = array(
    'streamCtag' => NULL,
  );
  $data = json_encode($data);
  $response = drupal_http_request($url, $headers, $method, $data, $retry, $timeout);
  $stream = json_decode($response->data, FALSE);
  
  // Build the list of guids.
  $checksums = array();
  $guids = array();
  foreach ($stream->photos as $photo) {
    $guids[] = $photo->photoGuid;
    $test = (array) $photo->derivatives;
    ksort($test);
    $test = array_pop($test);
    $checksums[] = $test->checksum;
  }
  $guids = array_unique($guids);
  $asset_url = $api . '/A45qXGF1Qtibd/sharedstreams/webasseturls';
  
  // Create the save folder.
  if (!is_dir($save_path)) {
    if (!mkdir($save_path)) {
      drush_print(dt("Unable to create the export folder."));
      return;
    }
  }
  if (!is_writable($save_path)) {
    drush_print(dt("Unable to write to the export folder."));
    return;
  }
  drush_print(dt("Initializing the export folder."));
    
  // Work through the guids in batches and download the images (image URLs are only valid for a brief period).
  while (!empty($guids)) {
    $data = array(
      'photoGuids' => array_slice($guids, 0, 5)
    );
    $guids = array_slice($guids, 5);
    $data = json_encode($data);
    $response = drupal_http_request($asset_url, $headers, $method, $data, $retry, $timeout);
    $assets = json_decode($response->data);
    if (!is_object($assets->items)) {
      var_dump($assets, $response);
      return;
      continue;
    }
    
    // Add the assets to the zip.
    $urls = array();
    foreach ($assets->items as $checksum => $item) {
      if (!in_array($checksum, $checksums)) {
        continue;
      }
      $url = $assets->locations->{$item->url_location}->scheme . '://' . $assets->locations->{$item->url_location}->hosts[0] . $item->url_path;
      $urls[] = $url;
    }
    if (empty($urls)) {
      drush_print(dt("Unable to locate any URLs."));
      continue;
    }
    $urls = array_unique($urls);
    $pairs = array();
    foreach ($urls as $i => $url) {
      if (preg_match('@^.*/(.*?)\[email protected]', $url, $arr)) {
        $localname = $arr[1];
      }
      else {
        $localname = "New File $i.jpg";
      }
      $localname = urldecode($localname);
      $localname = preg_replace('@[^a-zA-Z0-9\\\\/\. \-][email protected]', '_', $localname);
      $pairs[$localname] = $url;
    }
    $urls = $pairs;
    ksort($urls);
    
    // Export the images.
    foreach ($urls as $localname => $url) {
      $localname = $save_path . DIRECTORY_SEPARATOR . $localname;
      if (is_file($localname)) {
        drush_print(dt("Skipping: !path", array('!path' => $localname)));
        continue;
      }
      drush_print(dt("Downloading: !path", array('!path' => $localname)));
      $response = drupal_http_request($url, $headers);
      if ($response->code == 200) {
        file_put_contents($localname, $response->data);
      }
      else {
        drush_print(dt("Error with download"), 3);
      }
    }
  }
}


blog comments powered by Disqus