Over the years, my wife and I have taken a fair few digital photos with a variety of cameras and phones. Every single device has a slightly different naming convention and to be honest, none of them work for me.
I want photos to be in a structured folder structure based on years and months, I also want the photos to be named based on the date and time they were taken. Luckily, all of the devices we have used to take photos also tag the photos with the date and time the photo was taken.
What I want to achieve
When we have photos, I want us to be able to copy them to a folder and then run a Powershell script that will rename the files and move them to the appropriate position in the folder structure.
The name will be YYYYMMDD-HHmin-nnn.jpg and the folder structure will be YYYYMM
The Script
Parameters
Two mandatory parameters are defined
- -SourcePath: The location where the files are initially located
- -TargetPath: The route of the location where the files will be moved to
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True,Position=1)]
[string]$SourcePath,
[Parameter(Mandatory=$True)]
[string]$TargetPath
)
Each file in the SourcePath location is then processed.
Exif Date Taken
The key piece of data I need is the EXIF Date Taken value. I found a blog from Chris Warwick that provides two functions for getting and updating the Date Taken value which work very well (http://chrisjwarwick.wordpress.com/2011/11/08/modify-date-taken-values-on-photos-with-powershell-the-update-exifdatetaken-script-cmdlet-2/)
#Enhance the file with the ExifDateTaken value
$EnhancedFile = Get-ExifDateTaken $RawFile
With this piece of data I extract the year and the month to be able to build the folder structure and the core of the file name.
#Get the EXifDateTaken value so it can be used to generate the file structure and file name
$DateTaken = $EnhancedFile.ExifDateTaken.ToString()
$Year = [String]::Join("",[Char]$DateTaken[6],[Char]$DateTaken[7],[Char]$DateTaken[8],[Char]$DateTaken[9])
$Month = [String]::Join("",[Char]$DateTaken[3],[Char]$DateTaken[4])
#Build new file name
$NewName = [String]::Join("",$Year,$Month,[Char]$DateTaken[0],[Char]$DateTaken[1],"-",[Char]$DateTaken[11],[Char]$DateTaken[12],[Char]$DateTaken[14],[Char]$DateTaken[15],[Char]$DateTaken[17],[Char]$DateTaken[18])
Target Directory Structure
The target file structure is created if it does not already exist.
#Check of target year directory exists, if not create it
$FilePath = "$TargetPath$Year"
if (!(Test-Path -Path $FilePath)) {
New-Item -ItemType directory -Path $FilePath
}
#Check of target month directory exists, if not create it
$FilePath += "$Month"
if (!(Test-Path -Path $FilePath)) {
New-Item -ItemType directory -Path $FilePath
}
Existing Files
As more than one photo may have been taken during the same second, the filename will have a suffix of 000 or, if there are multiple files, then they will be numbered, e.g. 001, 002, etc. The value is determined by the number of files that have already been renamed and moved to the target location.
#Check for existing files with the same name and build the suffix for multiple files
$ExistingFiles = Get-ChildItem "$FilePath$NewName\*"
try {
$ExistingFileCount = $ExistingFiles.count
Write-Host "Existing File Count: $($ExistingFileCount)"
$NewName += "-" + $ExistingFileCount.ToString("000")
} catch {
$NewName += "-000"
}
Target File Name
The path is then put together with the core file name, the suffix and the file type.
#Build final new name, including path
$NewName = "$($TargetPath)$($Year)$($Month)$($NewName).jpg"
Move the File
Finally, the file is moved to the target location.
#Move the file to the final location
Move-Item -Path $RawFile.FullName -Destination $NewName
Errors
This script is a first draft, and errors will be produced of the photos do not have the EXIF Date Taken value set to a legitimate value. In this case, the files will not be processed.
Safety First
Don’t forget to back up your files before running this script.
How to Run the Scripts
There are two scripts to download and run:
- ExifDateTime.ps1 – This needs to be executed to make the functions it contains available to the next script.
- OrganisePhotos.ps1 – This script will be executed with two parameters
To execute the script:
.OrganisePhotos.ps1 -SourcePath "C:source" -TargetPath "C:target"