Technical blog with tips and tricks for everything and more...
Featured Post
Organize and rename photos by EXIF data with PowerShell
This PowerShell script organizes and renames all photos in a selected folder using EXIF data. It will also create thumbnails of the images i...
Showing posts with label PowerShell. Show all posts
Showing posts with label PowerShell. Show all posts
Saturday, May 23, 2015
Thursday, May 22, 2014
Organize and rename photos by EXIF data with PowerShell
This PowerShell script organizes and renames all photos in a selected folder using EXIF data. It will also create thumbnails of the images into a thumbs folder.
Simply copy the script into a PowerShell ISE script window and execute! You will be prompted for a Source and Target folder.
Example:
Source:
Target:
Simply copy the script into a PowerShell ISE script window and execute! You will be prompted for a Source and Target folder.
Example:
Source:
- unsorted\DSC_0236.jpg
- unsorted\DSC_0258.jpg
- unsorted\IMG_0258.jpg
Target:
- sorted\2014\05_May\2014-05-01_19-08-26_258.jpg
- sorted\2014\05_May\thumbs\2014-05-01_19-08-26_258_thumb_400x400.jpg
# ==============================================================================================
#
# Microsoft PowerShell Source File
#
# This script will organize photo and video files by renaming the file based on the date the
# file was created and moving them into folders based on the year and month. It will also append
# an index number to the end of the file name just to avoid name collisions. The script will
# look in the SourceRootPath (recursing through all subdirectories) for any files matching
# the extensions in FileTypesToOrganize. It will rename the files and move them to folders under
# DestinationRootPath, e.g. DestinationRootPath\2011\02_February\2011-02-09_21-41-47_680.jpg
#
# JPG files contain EXIF data which has a DateTaken value. Other media files have a MediaCreated
# date.
#
# The code for extracting the EXIF DateTaken is based on a script by Kim Oppalfens:
# http://blogcastrepository.com/blogs/kim_oppalfenss_systems_management_ideas/archive/2007/12/02/organize-your-digital-photos-into-folders-using-powershell-and-exif-data.aspx
# ==============================================================================================
# Modified by Brock Hensley brock@brockhensley.com
# - Prompts for many options (source/destination folders, label, thumbnail sizes (multiple at once even))
# - Resize thumbnail rotates orientation appropriately
# - Named by incremental number instead of random number
# - Ability to apply time fix for multiple cameras with out of sync clocks
# ==============================================================================================
[Reflection.Assembly]::LoadFile("C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Drawing.dll")
function Select-Folder($message='Select folder', $path = 0) {
$object = New-Object -comObject Shell.Application
$folder = $object.BrowseForFolder(0, $message, 0, $path)
if ($folder -ne $null) {
return $folder.self.Path
}
}
# Defaults
$FileTypesToOrganize = @("*.jpg","*.jpeg")
# Prompts to override defaults
$Label = ""
$prompt = Read-Host "What to name files (default is date taken timestamp + index#: YYYY-MM-DD_HH-MM-SS_x#)"
if ($prompt -ne $null -and $prompt -ne "") {$Label = $prompt}
$Index = 10000
$prompt = Read-Host "Index starting number (default: $Index)"
if ($prompt -ne $null -and $prompt -ne "") { $Index = [Convert]::ToInt32($prompt) }
$SourceRootPath = "C:\UnsortedPhotos"
$prompt = Select-Folder "Select source folder (default: $SourceRootPath )"
if ($prompt -ne $null -and $prompt -ne "") {$SourceRootPath = $prompt}
$DestinationRootPath = "C:\SortedPhotos"
$prompt = Select-Folder "Select destination folder (default: $DestinationRootPath )"
if ($prompt -ne $null -and $prompt -ne "") {$DestinationRootPath = $prompt}
$CopyRenameOrig = $true
$prompt = Read-Host "Copy and Rename originals by date? ([y]/n)"
if ($prompt -ne $null -and $prompt -ne "" -and $prompt.ToLower() -eq "n") {
$CopyRenameOrig = $false
}
$CreateThumb = $true
$prompt = Read-Host "Create thumbnails? ([y]/n)"
if ($prompt -ne $null -and $prompt -ne "" -and $prompt.ToLower() -eq "n") {
$CreateThumb = $false
}
if ($CreateThumb -eq $true)
{
$ThumbSizes = "128, 256, 512, 1024"
Write-Host "Enter thumbnail sizes to create in comma seperated list. Default:" $ThumbSizes
Write-Host "or enter N to skip creating thumbnails"
$prompt = Read-Host "Thumbnail sizes"
if ($prompt -ne $null -and $prompt -ne "") {
if ($prompt.ToLower() -eq "n")
{
$CreateThumb = $false
}
else {
$ThumbSizes = $prompt
}
}
}
# Functions
function GetMediaCreatedDate($File) {
$Shell = New-Object -ComObject Shell.Application
$Folder = $Shell.Namespace($File.DirectoryName)
$CreatedDate = $Folder.GetDetailsOf($Folder.Parsename($File.Name), 191).Replace([char]8206, ' ').Replace([char]8207, ' ')
if (($CreatedDate -as [DateTime]) -ne $null) {
return [DateTime]::Parse($CreatedDate)
} else {
return $null
}
}
function ConvertAsciiArrayToString($CharArray) {
$ReturnVal = ""
foreach ($Char in $CharArray) {
$ReturnVal += [char]$Char
}
return $ReturnVal
}
function GetExifDateTaken($File) {
$FileDetail = New-Object -TypeName System.Drawing.Bitmap -ArgumentList $File.Fullname
if ($FileDetail -ne $null)
{
$DateTimePropertyItem = $FileDetail.GetPropertyItem(36867)
$FileDetail.Dispose()
}
if ($DateTimePropertyItem -eq $null)
{
return $null
}
$Year = ConvertAsciiArrayToString $DateTimePropertyItem.value[0..3]
$Month = ConvertAsciiArrayToString $DateTimePropertyItem.value[5..6]
$Day = ConvertAsciiArrayToString $DateTimePropertyItem.value[8..9]
$Hour = ConvertAsciiArrayToString $DateTimePropertyItem.value[11..12]
$Minute = ConvertAsciiArrayToString $DateTimePropertyItem.value[14..15]
$Second = ConvertAsciiArrayToString $DateTimePropertyItem.value[17..18]
# CameraA and CameraB taking pictures of same event
# ...CameraB timestamps don't match CameraA due to DST etc.
# Set $timefix to $true to make adustments here
# otherwise by default this should be set to $false
$timefix = $false
if ($timefix -eq $true)
{
$Hour = ([Convert]::ToInt32($Hour) + 1).ToString()
$Minute = ([Convert]::ToInt32($Minute) - 18).ToString()
}
$DateString = [String]::Format("{0}-{1}-{2} {3}:{4}:{5}", $Year, $Month, $Day, $Hour, $Minute, $Second).Trim()
if (($DateString -as [DateTime]) -ne $null) {
return [DateTime]::Parse($DateString)
} else {
return $null
}
}
function GetCreationDate($File) {
if ($File.Extension.ToLower() -eq ".jpg" -or $File.Extension.ToLower() -eq ".jpeg")
{
$CreationDate = GetExifDateTaken($File)
}
else
{
$CreationDate = GetMediaCreatedDate($File)
}
return $CreationDate
}
function BuildDesinationPath($Path, $Date) {
return [String]::Format("{0}\{1}\{2}_{3}", $Path, $Date.Year, $Date.ToString("MM"), $Date.ToString("MMMM"))
}
function BuildThumbDesinationPath($Path, $Date, $ThumbSizeLabel) {
return [String]::Format("{0}\{1}\{2}_{3}\thumbs\{4}", $Path, $Date.Year, $Date.ToString("MM"), $Date.ToString("MMMM"), $ThumbSizeLabel)
}
function BuildNewFilePath($Path, $Date, $Extension, $Index, $Label) {
if ($Label -eq "")
{$Label = $Date.ToString("yyyy-MM-dd_HH-mm-ss")}
return [String]::Format("{0}\{1}_x{2}{3}", $Path, $Label, $Index, $Extension.ToLower())
if ($Label -ne "")
{
return [String]::Format("{0}\{1}_x{2}{3}", $Path, $Label, $Index, $Extension.ToLower())
}
return [String]::Format("{0}\{1}_x{2}{3}", $Path, $Date.ToString("yyyy-MM-dd_HH-mm-ss"), $Index, $Extension.ToLower())
}
function BuildThumbFilePath($Path, $Date, $Extension, $Size, $Index, $Label) {
if ($Label -eq "")
{$Label = $Date.ToString("yyyy-MM-dd_HH-mm-ss")}
return [String]::Format("{0}\thumbs\{5}\{1}_x{2}_{3}{4}", $Path, $Label, $Index, $Size, $Extension.ToLower(), $Size)
}
function ScaleImage($image, $maxWidth, $maxHeight)
{
try { $prop = $image.GetPropertyItem(274) } catch {}
if ($prop -ne $null) {
$orientation = [int]$prop.Value[0];
}
else
{
$orientation = 1
}
switch ($orientation)
{
1 {
# No rotation required.
break;
}
2 {
$image.RotateFlip("RotateNoneFlipX");
break;
}
3 {
$image.RotateFlip("Rotate180FlipNone");
break;
}
4 {
$image.RotateFlip("Rotate180FlipX");
break;
}
5 {
$image.RotateFlip("Rotate90FlipX");
break;
}
6 {
$image.RotateFlip("Rotate90FlipNone");
break;
}
7 {
$image.RotateFlip("Rotate270FlipX");
break;
}
8 {
$image.RotateFlip("Rotate270FlipNone");
break;
}
}
[Double]$ratioX = $maxWidth / $image.Width;
[Double]$ratioY = $maxHeight / $image.Height;
$ratio = [System.Math]::Min($ratioX, $ratioY);
[int]$newWidth = $image.Width * $ratio;
[int]$newHeight = $image.Height * $ratio;
# Now calculate the X,Y position of the upper-left corner (one of these will always be zero)
[int]$posX = [Convert]::ToInt32(($maxWidth - ($image.Height * $ratio)) / 2);
[int]$posY = [Convert]::ToInt32(($maxHeight - ($image.Width * $ratio)) / 2);
Write-Host -NoNewline " "$newWidth"x"$newHeight
$newImage = New-Object -TypeName System.Drawing.Bitmap -ArgumentList $newWidth,$newHeight
$g=[System.Drawing.Graphics]::FromImage($newImage);
$g.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic # use high quality resize algorythm
$g.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::HighQuality
$g.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality
$g.CompositingQuality = [System.Drawing.Drawing2D.CompositingQuality]::HighQuality
$g.Clear([System.Drawing.Color]::White)
$g.DrawImage($image, 0, 0, $newWidth, $newHeight)
return $newImage;
}
function CreateDirectory($Path){
if (!(Test-Path $Path)) {
New-Item $Path -Type Directory | Out-Null
}
}
function ConfirmContinueProcessing() {
$Response = Read-Host "Continue? (Y/N)"
if ($Response.Substring(0,1).ToUpper() -ne "Y") {
break
}
}
# Begin
$StartTime = Get-Date
Write-Host "*Begin" $StartTime
$Files = Get-ChildItem $SourceRootPath -Recurse -Include $FileTypesToOrganize
$Count = 0
$Existing = 0
$ThumbCount = 0
$ExistingThumb = 0
$Unknown = 0
$LastCreationDate = $null
foreach ($File in $Files) {
$CreationDate = GetCreationDate($File)
if ($CreationDate -eq $null -or ($CreationDate -as [DateTime]) -eq $null) {
$Unknown++
Write-Host " Unable to determine creation date of file. " $File.FullName
if ($LastCreationDate -eq $null)
{
$CreationDate = [DateTime]::Now
}
else
{
$CreationDate = $LastCreationDate
}
Write-Host " (borrowing creation date: " $CreationDate " )"
#ConfirmContinueProcessing
}
$LastCreationDate = $CreationDate
$DestinationPath = BuildDesinationPath $DestinationRootPath $CreationDate
CreateDirectory $DestinationPath
$NewFilePath = BuildNewFilePath $DestinationPath $CreationDate $File.Extension $Index $Label
Write-Host ""
Write-Host ""
Write-Host "[Orig] " $File.FullName
Write-Host "--New: " $NewFilePath
if ($CopyRenameOrig -eq $true)
{
if (Test-Path $NewFilePath) {
$Existing++
Write-Host " -- **** (skipping) Unable to make file. File already exists: $NewFilePath ****"
#ConfirmContinueProcessing
} else {
Copy-Item $File.FullName $NewFilePath
$Count++
}
}
if ($CreateThumb -eq $true)
{
Write-Host " -- Creating thumbnails: " -NoNewline
$ThumbSizes.Split(",") | ForEach {
$ThumbSize = $_.Trim();
$ThumbSizeLabel = $ThumbSize + "x" + $ThumbSize
$DestinationThumbPath = BuildThumbDesinationPath $DestinationRootPath $CreationDate $ThumbSizeLabel
CreateDirectory $DestinationThumbPath
$ThumbFilePath = BuildThumbFilePath $DestinationPath $CreationDate $File.Extension $ThumbSizeLabel $Index $Label
if (Test-Path $ThumbFilePath) {
$ExistingThumb++;
Write-Host " -- **** (skipping thumb: $ThumbFilePath) ****"
} else {
$full = [System.Drawing.Image]::FromFile($File.FullName);
$newImage = ScaleImage $full $ThumbSize $ThumbSize
#Encoder parameter for image quality
$myEncoder = [System.Drawing.Imaging.Encoder]::Quality
$encoderParams = New-Object System.Drawing.Imaging.EncoderParameters(1)
$encoderParams.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter($myEncoder, 100)
# get codec
$myImageCodecInfo = [System.Drawing.Imaging.ImageCodecInfo]::GetImageEncoders()|where {$_.MimeType -eq 'image/jpeg'}
if ($newImage -ne $null){
$newImage.Save($ThumbFilePath, $myImageCodecInfo, $($encoderParams));
}
$full.Dispose();
if ($newImage -ne $null){$newImage.Dispose();}
}
$ThumbCount++
}
}
$Index++
}
$TimeSpan = New-TimeSpan $StartTime (Get-Date)
Write-Host ""
Write-Host "**** Done! Total:" $Count " Existing (skipped):" $Existing " Unknown dates:" $Unknown " Duration:" $TimeSpan.Minutes"min " $TimeSpan.Seconds "sec"
Subscribe to:
Posts (Atom)