PoshCode Logo PowerShell Code Repository

PowerSmug - Sync Smugmug (modification of post by view diff)
embed code: <script type="text/javascript" src="http://PoshCode.org/embed/796"></script>download | new post

Syncronize local folders with Smugmug using this powershell script.

  1. # PowerSmug photo sync script v1.0
  2. # This PowerShell script will syncronize a folder of images with a users SmugMug account
  3. # Please set the appropriate variables in the User Defined Variables region
  4. # For more information visit
  5. #
  6. # Images are uploaded to a gallery with the same name as the folder they are contained in.
  7. # All folders below the Photo Directory path and the images they contain will be uploaded to SmugMug
  8. # Folders that end in _ are ignored, so if you don't want to sync a folder with SmugMug, just add an underscore at the end
  9. #
  10. # Copyright 2008 Shell Tools, LLC
  11.  
  12. #Region User Defined Variables
  13.  
  14. $ApiKey = 'uVUvCbXP3f6MgO9wIRJn21YCIgEidVly'
  15. $EmailAddress = '[SmugMug Email]'
  16. $Password = '[SmugMug Password]'
  17. $PhotoDirectory = '[Path To Photos]'
  18. $filesToInclude = @('*.jpg','*.png','*.tif','*.cr2')
  19. $worldSearchable = 1
  20. $smugSearchable = 1
  21.  
  22. # Professional Accounts Only
  23. $watermark=0
  24. $watermarkId=0
  25.  
  26. #Email Variables
  27. $smtpServer = '[SMTP Server]'
  28. $smtpUser = '[SMTP User]'
  29. $smtpPassword = '[SMTP Password]'
  30. $smtpFrom = 'PowerSmug@shelltools.net'
  31.  
  32. #EndRegion
  33.  
  34. #Region Global Variables
  35.  
  36. $baseUrl = 'http://api.smugmug.com/hack/rest/1.2.0/'
  37. $userAgent = 'PowerSmug v1.0'
  38. $logFile = 'PowerSmug.log'
  39. $script:SessionId = $null
  40. $script:startStringState = $false
  41. $script:valueState = $false
  42. $script:arrayState = $false
  43. $script:saveArrayState = $false
  44. $script:datFileDirty = $false
  45. $script:datFile = @()
  46. $script:albumList = $null
  47. $script:imageList = $null
  48.  
  49. #EndRegion
  50.  
  51. #region Helper Functions
  52.  
  53. function SendEmail([string] $to, [string] $subject, [string] $msg)
  54. {
  55.         $smtpMail = new-Object System.Net.Mail.SmtpClient $smtpServer
  56.         $smtpMail.DeliveryMethod = [System.Net.Mail.SmtpDeliveryMethod]'Network'
  57.         $smtpMail.Credentials = new-Object System.Net.NetworkCredential $smtpUser, $smtpPassword
  58.  
  59.         $smtpMail.Send($smtpFrom, $to, $subject, $msg)
  60. }
  61.  
  62. function Get-MD5([System.IO.FileInfo] $file = $(Throw 'Usage: Get-MD5 [System.IO.FileInfo]'))
  63. {
  64.         # This Get-MD5 function sourced from:
  65.     # http://blogs.msdn.com/powershell/archive/2006/04/25/583225.aspx
  66.         $stream = $null;
  67.         $cryptoServiceProvider = [System.Security.Cryptography.MD5CryptoServiceProvider];
  68.         $hashAlgorithm = new-object $cryptoServiceProvider
  69.         $stream = $file.OpenRead();
  70.         $hashByteArray = $hashAlgorithm.ComputeHash($stream);
  71.         $stream.Close();
  72.  
  73.         ## We have to be sure that we close the file stream if any exceptions are thrown.
  74.         trap
  75.     {
  76.                 if ($stream -ne $null) { $stream.Close(); }
  77.                 break;
  78.         }
  79.  
  80.     # Convert the MD5 hash to Hex
  81.         $hashByteArray | foreach { $result += $_.ToString("X2") }
  82.  
  83.         return $result
  84. }
  85.  
  86. function SendWebRequest([string]$method, $queryParams, $ssl = $false)
  87. {
  88.         $url = $baseUrl
  89.         if ($ssl -eq $true) {
  90.                 $url = $url.Replace("http", "https")
  91.         }
  92.  
  93.         $url += "?method=$method"
  94.  
  95.         foreach ($key in $queryParams.Keys) {
  96.                 $url += "&$key=" + $queryParams[$key]
  97.         }
  98.  
  99.         $wc = New-Object Net.WebClient
  100.  
  101.         $wc.Headers.Add("user-agent", $userAgent)
  102.         [xml]$webResult = $wc.DownloadString($url)
  103.        
  104.         CheckResponseForError $webResult
  105.  
  106.         return $webResult.rsp
  107. }
  108.  
  109. # Adds image information to our data file
  110. function AddFile ([System.IO.FileInfo]$file, $smugId) {
  111.  
  112.         $a = 1 | Select-Object Name, SmugId, LastModifiedTime;
  113.         $a.Name = $file.Name
  114.         $a.SmugId = $smugId
  115.         $a.LastModifiedTime = $file.LastWriteTimeUtc.ToString()
  116.  
  117.         $script:datFile += $a
  118. #       $script:datFileDirty = $true
  119.        
  120.         #we are now saving the file after each upload to prevent duplicates when there is a failure
  121.         AppendDataFile $datFilePath $a
  122. }
  123.  
  124. # writes the data file to the local directory
  125. # each directory will have a data file with information for images contained in it
  126. function SaveDataFile($filePath, $clearFile=$true)
  127. {
  128.         if ($script:datFileDirty -eq $false) { $script:datFile = @(); return }
  129.  
  130.         [System.IO.File]::Delete($filePath)
  131.        
  132.         $sortedFile = $script:datFile | sort-Object -property Name
  133.         $sortedFile | Export-Csv $filePath
  134.        
  135.         $script:datFileDirty = $false
  136.  
  137.         # mark the file as hidden
  138.         ls $filePath | foreach { $_.Attributes = $_.Attributes -bor [System.IO.FileAttributes]::Hidden }
  139.  
  140.         if ($clearFile -eq $true) {
  141.                 # clear the variable
  142.                 $script:datFile = @()
  143.         }
  144. }
  145.  
  146. # appends a new row to the data file
  147. function AppendDataFile($filePath, $record)
  148. {
  149.         if (test-Path $datFilePath) {
  150.                 $newLine = [System.String]::Format('{0},{1},"{2}"', $a.Name, $a.SmugId, $a.LastModifiedTime)
  151.                 Add-Content $filePath $newLine
  152.         }
  153.         else
  154.         {
  155.                 $script:datFileDirty = $true
  156.                 SaveDataFile $filePath $false
  157.         }
  158. }
  159.  
  160. # ensure our web request was successful
  161. function CheckResponseForError($xmlResponse)
  162. {
  163.         if ($xmlResponse.rsp.stat -eq 'fail')   {
  164.                 throw $webResult.rsp.err.msg
  165.         }
  166. }
  167.  
  168. #endregion
  169.  
  170. #region Login/Logout
  171.  
  172. function Login()
  173. {
  174.         if ($script:SessionId -ne $null) { return }
  175.  
  176.         $ht = @{APIKey=$ApiKey;EmailAddress=$EmailAddress;Password=$Password}
  177.         $loginResult = SendWebRequest "smugmug.login.withPassword" $ht $true
  178.        
  179.         if ($loginResult.stat -eq "ok") {
  180.                 $script:SessionId = $loginResult.Login.Session.id
  181.         }
  182.         else {
  183.                 Throw "Error on login: " + $loginResult.Message
  184.         }
  185. }
  186.  
  187. function Logout()
  188. {
  189.         if ($script:SessionId -eq $null) { return }
  190.  
  191.         $ht = @{SessionID=$script:SessionId}
  192.         $logoutResult = SendWebRequest "smugmug.logout" $ht
  193. }
  194.  
  195. #endregion
  196.  
  197. #region Images
  198.  
  199. # this method is only needed if we cannot find the PowerSmug.dat file.
  200. # preventing us from uploading duplicate images
  201. function GetImage($name, $album)
  202. {
  203.         Login
  204.        
  205.         $image = $script:imageList.Images | Where-Object { $_.FileName -eq $name }
  206.         if ($image -ne $null) { return $image }
  207.  
  208.         # we are using heavy because we need the file name
  209.         $ht = @{SessionID=$script:SessionId;AlbumID=$album.id;Heavy=1;AlbumKey=$album.Key}
  210.         [xml]$script:imageList = SendWebRequest "smugmug.images.get" $ht
  211.        
  212.         return $script:imageList.Images.Image | Where-Object { $_.FileName -eq $name } 
  213. }
  214.  
  215. function UploadFile($file, $albumName)
  216. {
  217.         $album = GetAlbum $albumName
  218.         # $image = GetImage $file.Name $album  
  219.         # if ($image -ne $null) { return $image.id }
  220.  
  221.         $url = "http://upload.smugmug.com/" + $file.Name
  222.  
  223.         $wc = New-Object Net.WebClient
  224.  
  225.         $hash = Get-MD5 $file
  226.  
  227.         $wc.Headers.Add("user-agent", $userAgent)
  228.         $wc.Headers.Add("ContentMd5", $hash)
  229.         $wc.Headers.Add("X-Smug-FileName", $file.Name)
  230.         $wc.Headers.Add("X-Smug-AlbumID", $album.id)
  231.         $wc.Headers.Add("X-Smug-SessionID", $script:SessionId)
  232.         $wc.Headers.Add("X-Smug-Version", "1.2.0")
  233.         $wc.Headers.Add("X-Smug-ResponseType", "REST")
  234.         $webResult = $wc.UploadFile($url, "PUT", $file.FullName)
  235.         [xml]$webResult = [System.Text.Encoding]::ASCII.GetString($webResult)
  236.        
  237.         CheckResponseForError $webResult
  238.        
  239.         return $webResult.rsp.Image.id
  240. }
  241.  
  242. function UploadExistingFile($file, $id)
  243. {
  244.         Login
  245.  
  246.         $url = "http://upload.smugmug.com/" + $file.Name
  247.  
  248.         $wc = New-Object Net.WebClient
  249.  
  250.         $hash = Get-MD5 $file
  251.  
  252.         $wc.Headers.Add("user-agent", $userAgent)
  253.         $wc.Headers.Add("ContentMd5", $hash)
  254.         $wc.Headers.Add("X-Smug-FileName", $file.Name)
  255.         $wc.Headers.Add("X-Smug-ImageID", $id)
  256.         $wc.Headers.Add("X-Smug-SessionID", $script:SessionId)
  257.         $wc.Headers.Add("X-Smug-Version", "1.2.0")
  258.         $wc.Headers.Add("X-Smug-ResponseType", "REST")
  259.         $webResult = $wc.UploadFile($url, "PUT", $file.FullName)
  260.  
  261.         [xml]$webResult = [System.Text.Encoding]::ASCII.GetString($webResult)
  262.        
  263.         CheckResponseForError $webResult
  264.        
  265.     return $webResult.rsp
  266. }
  267.  
  268. #endregion
  269.  
  270. #region Album
  271.  
  272. function GetAlbumList($forceRefresh=$false) {
  273.         Login
  274.  
  275.         if ($forceRefresh -eq $false) {
  276.                 if ($script:albumList -ne $null) { return }
  277.         }
  278.        
  279.         $ht = @{SessionID=$script:SessionId}
  280.         $script:albumList = SendWebRequest "smugmug.albums.get" $ht
  281. }
  282.  
  283. function GetAlbum($name)
  284. {
  285.         #before we create an album ensure it doesn't already exist
  286.         GetAlbumList
  287.  
  288.         $album = $script:albumList.Albums.Album | Where-Object { $_.Title -eq $name }
  289.         if ($album -ne $null) { return $album }
  290.  
  291.     Write-Host "Creating album: $name"
  292.         $ht = @{SessionID=$script:SessionId;Title=$name;CategoryID=0;Public=0;
  293.     X2Larges=0;X3Larges=0;Originals=0;Watermarking=$watermark;
  294.     WatermarkID=$watermarkId;WorldSearchable=$worldSearchable;SmugSearchable=$smugSearchable}
  295.     $result = SendWebRequest "smugmug.albums.create" $ht
  296.        
  297.         # be sure we refresh the album list after creation
  298.         GetAlbumList $true
  299.        
  300.         return $result.Album
  301. }
  302.  
  303. #endregion
  304.  
  305. #region Process File
  306.  
  307. function ProcessFile([System.IO.FileInfo]$file, $albumName)
  308. {
  309.         $photoObject = $script:datFile | Where-Object { $_.Name -eq $file.Name }
  310.  
  311.         if ($photoObject -ne $null) {
  312.                 if ($photoObject.LastModifiedTime -ne $file.LastWriteTimeUtc.ToString()) {
  313.                         # file has been modified, so re-upload the file
  314.             write-Host "Updating existing file: " $file.FullName
  315.                         UploadExistingFile $file $photoObject.SmugId
  316.                         $photoObject.LastModifiedTime = $file.LastWriteTimeUtc.ToString()
  317.            
  318.             # mark the dat file as dirty so it will be saved after processing this folder
  319.                         $script:datFileDirty = $true
  320.                 }
  321.         }
  322.         else {
  323.                 #file doesn't exist in local file, upload to SmugMug
  324.         write-Host "Uploading new file: " $file.FullName
  325.                 $id = UploadFile $file $albumName
  326.                 AddFile $file $id
  327.         }
  328. }
  329.  
  330. #endregion
  331.  
  332. #region Main Script
  333.  
  334. # this section will look through all sub-directories of $PhotoDirectory and upload the images to SmugMug
  335. Get-ChildItem -recurse $PhotoDirectory | Where-Object { $_.Attributes -band [System.IO.FileAttributes]::Directory } | foreach {
  336.         # don't process folders that end in _
  337.         if ($_.FullName.EndsWith("_") -eq $false) {
  338.                 $datFilePath = $_.FullName + "\PowerSmug.dat"
  339.                 if (test-Path $datFilePath) {
  340.                         # casting as array to ensure we have an array returned
  341.                         [array]$script:datFile = import-Csv $datFilePath
  342.                 }
  343.        
  344.                 $albumName = $_.FullName.Remove(0, $PhotoDirectory.Length).Trim('\')
  345.        
  346.                 $path = $_.FullName + "\*"
  347.                 foreach ($file in get-ChildItem $path -include $filesToInclude) {
  348.                         ProcessFile $file $albumName
  349.                 }
  350.                 SaveDataFile $datFilePath
  351.                 $script:imageList = $null
  352.         }
  353. }
  354.  
  355. Logout
  356.  
  357. $date = Get-Date
  358. Write-Host "Script Completed: $date"
  359.  
  360. trap [Exception] {
  361.                 $date = Get-Date
  362.                 $Msg = $date.ToString() + " ; " + $_.Exception.GetType().FullName + " ; " + $_.Exception.Message
  363.                 Add-Content $logFile $Msg
  364.                 SendEmail $EmailAddress "PowerSmug Error" $Msg
  365.                 break
  366.         }
  367.  
  368. #endregion

Submit a correction or amendment below (
click here to make a fresh posting)
After submitting an amendment, you'll be able to view the differences between the old and new posts easily.

Syntax highlighting:


Remember me