commit 6e9fc6e84997ba4df3e07a037255123aa8c1ca0d Author: Chris Date: Mon Jan 6 22:35:02 2025 +0200 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..46b134b --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +ÿþ \ No newline at end of file diff --git a/organisephotos.ps1 b/organisephotos.ps1 new file mode 100644 index 0000000..92ed637 --- /dev/null +++ b/organisephotos.ps1 @@ -0,0 +1,466 @@ +############### GLOBAL ############## +$LogPath = "C:/Temp/OrgPhonePhotos.log" + +############### MAIN ############### +# main function +# +$Main = +{ + [CmdletBinding()] + Param( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] $SourceFolder, + + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] $DestFolder, + + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] $FolderForDuplicates, + + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] $LogFile + + ) + $header = "$("-"*80)`n`n$(Get-Date -UFormat ""%A %Y-%m-%d %T"")`n" + Write-Output $header | Tee-Object -FilePath $LogFile -Append + $LogPath = (Get-Item $LogFile).FullName + validatedest $SourceFolder $DestFolder $FolderForDuplicates + cleanfolder $SourceFolder + remove-emptyfolders $SourceFolder + $medialist=[System.Collections.Generic.List[object]]::new() + $Files_jpg = Get-ChildItem -Path $SourceFolder -File -Filter "*.jpg" -Recurse + $Files_raw = Get-ChildItem -Path $SourceFolder -File -Filter "*.cr?" -Recurse + ForEach ($File in $Files_jpg) { + $medialist.Add($File) + } + ForEach ($File in $Files_raw) { + $medialist.Add($File) + } + foreach ($mediafile in $medialist) { + $xfoutput = @(C:\Tools\exiftool\exiftool.exe $mediafile.FullName) + $sourcefilefullname = $mediafile.FullName + # Write-Output $sourcefilefullname | Tee-Object -FilePath $LogPath -Append + $exiflist = @{} + foreach ($tagvalpair in $xfoutput) { + $arr=@($tagvalpair -split ":", 2) + $key=$arr[0] -replace ' ' -replace '/','_' + $value=$arr[1].Trim() + $exiflist[$key] = $value + } + $CreateDate = cv2datetime $exiflist['CreateDate'] + $Date_TimeOriginal = cv2datetime $exiflist['Date_TimeOriginal'] + $FileCreationDate_Time = cv2datetime $exiflist['FileCreationDate_Time'] + $FileModificationDate_Time = cv2datetime $exiflist['FileModificationDate_Time'] + $FileAccessDate_Time = cv2datetime $exiflist['FileAccessDate_Time'] + $skip = $false + # $time_arr = "" + if ($null -eq $CreateDate) { + if ($null -eq $Date_TimeOriginal) { + $filenamedate = validatedate $mediafile.Name + if ($null -eq $filenamedate) { + $earliest = getearliest $FileCreationDate_Time $FileModificationDate_Time $FileAccessDate_Time + $hint_time = $earliest.ToString('yyyy-MM-dd HH:mm:ss') + Write-Output "Skipped ""$sourcefilefullname"" : File has no usable ExifData! Earliest file time is $hint_time" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor DarkRed + $skip = $true + } + else { + Write-Output """$sourcefilefullname"" : File has no usable ExifData. Using date in filename." | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Yellow + $datestr = $filenamedate.ToString('yyyyMMdd') + } + } + else { + $datestr = $Date_TimeOriginal.ToString('yyyyMMdd') +# $time_arr = @($Date_TimeOriginal -split " ", 2) +# $datestr = $time_arr[0] -replace ":" +# if (($null -eq $datestr) -or ($datestr -eq "")) { +# Write-Output "Skipped ""$sourcefilefullname"" : File has no usable ExifData!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor DarkRed +# $skip = $true +# } + } + } + else { + $datestr = $CreateDate.ToString('yyyyMMdd') +# $time_arr = @($CreateDate -split " ", 2) +# $datestr = $time_arr[0] -replace ":" +# if (($null -eq $datestr) -or ($datestr -eq "")) { +# $time_arr = @($Date_TimeOriginal -split " ", 2) +# $datestr = $time_arr[0] -replace ":" +# if (($null -eq $datestr) -or ($datestr -eq "")) { +# Write-Output "Skipped ""$sourcefilefullname"" : File has no usable ExifData!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor DarkRed +# $skip = $true +# } +# } + } + $sourcefiledir = "$($mediafile.Directory)" + #Write-Output $time_arr + if (-not($skip)) { + #Write-Output "$($mediafile.Name) $datestr" + movetosubfolder "$($mediafile.Name)" "$datestr" "$sourcefiledir" "$DestFolder" "$FolderForDuplicates" + remove-emptyfolder "$sourcefiledir" + } + } +} +Function validatedate { + [CmdletBinding()] + Param( + [Parameter(Mandatory=$false)] + [string] $datetimestr + ) + if (($null -eq $datetimestr) -or ($datetimestr -eq "")) { + return $null + } + if ($datetimestr.Length -le 13) { + return $null + } + $datestart = 0 + $stryear = $datetimestr.substring($datestart,4) + if ($stryear -eq "IMG-") { + $datestart = 4 + $stryear = $datetimestr.substring($datestart,4) + if ($datetimestr.Length -le 17) { + return $null + } + } + $strmonth = $datetimestr.substring($datestart + 4,2) + $strday = $datetimestr.substring($datestart + 6,2) + $year = 0 + $month = 0 + $day = 0 + if (-not [int]::TryParse($stryear, [ref] $year)) { + return $null + } + if (-not [int]::TryParse($strmonth, [ref] $month)) { + return $null + } + if (-not [int]::TryParse($strday, [ref] $day)) { + return $null + } + if ($year -lt 1 -or $month -lt 1 -or $day -lt 1) { + return $null + } + try { + $datetime =[DateTime]"$stryear-$strmonth-$strday" + } + catch { + return $null + } + $now = Get-Date + if ($datetime -gt $now) { + return $null + } + return $datetime +} +Function getearliest { + [CmdletBinding()] + Param( + [Parameter(Mandatory=$false)] + [string] $datetimestr1, + + [Parameter(Mandatory=$false)] + [string] $datetimestr2, + + [Parameter(Mandatory=$false)] + [string] $datetimestr3 + ) + $result = [long]::MaxValue + if (-Not(($null -eq $datetimestr1) -or ($datetimestr1 -eq ""))) { + $datetime1 = [DateTime]$datetimestr1 + $time = $datetime1.Ticks + if ($time -lt $result) { + $result = $time + } + } + if (-Not(($null -eq $datetimestr2) -or ($datetimestr2 -eq ""))) { + $datetime2 = [DateTime]$datetimestr2 + $time = $datetime2.Ticks + if ($time -lt $result) { + $result = $time + } + } + if (-Not(($null -eq $datetimestr3) -or ($datetimestr3 -eq ""))) { + $datetime3 = [DateTime]$datetimestr3 + $time = $datetime3.Ticks + if ($time -lt $result) { + $result = $time + } + } + if ($result -eq [long]::MaxValue) { + return $null + } + return [DateTime]$result +} +Function cv2datetime { + [CmdletBinding()] + Param( + [Parameter(Mandatory=$false)] + [string] $datetimestr + ) + if (($null -eq $datetimestr) -or ($datetimestr -eq "")) { + return $null + } + $time_arr = @($datetimestr -split " ", 2) + $datestr = $time_arr[0] -replace ":" + $datestr = $datestr.Trim() + if (($null -eq $datestr) -or ($datestr -eq "")) { + $datestr = "" + } + else { + $datestr = $time_arr[0] -replace ":","-" + } + $timestr = $time_arr[1] -replace ":" + $timestr = $timestr.Trim() + if (($null -eq $timestr) -or ($timestr -eq "")) { + $timestr = "" + } + else { + $timestr = $time_arr[1] + } + $result = "$datestr $timestr".Trim() + if ($result -eq "") { + return $null + } + return [DateTime]$result +} +Function cleanfolder { + [CmdletBinding()] + Param( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] $folder + ) + $filelist = Get-ChildItem -Path $SourceFolder -File -Filter "ZbThumbnail.info" -Recurse + foreach ($file in $filelist) { + deletefile "$($file.FullName)" + } + $filelist = Get-ChildItem -Path $SourceFolder -File -Filter "Thumbs.db" -Recurse + foreach ($file in $filelist) { + deletefile "$($file.FullName)" + } +} +Function deletefile { + [CmdletBinding()] + Param( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] $filepath + ) + if(Test-Path -Path $filepath -PathType Leaf) { + Remove-Item $filepath + Write-Output "$filepath deleted!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Magenta + } +} +Function remove-emptyfolders { + [CmdletBinding()] + Param( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] $folder + ) + $folderlist = Get-ChildItem -Path $folder -Directory -Force -Recurse | Sort-Object FullName -Descending + foreach ($item in $folderlist) { + $folderdetail = $item.GetFileSystemInfos() | Measure-Object + If ($folderdetail.Count -eq 0) { + deletefolder "$($item.FullName)" + } + } +} +Function remove-emptyfolder { + [CmdletBinding()] + Param( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] $folder + ) + $count = (Get-ChildItem -Path $folder -Force | Measure-Object).Count + If ($count -eq 0) { + deletefolder "$folder" + } +} +Function deletefolder { + [CmdletBinding()] + Param( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] $folderpath + ) + if(Test-Path -Path $folderpath -PathType Container) { + Remove-Item $folderpath + Write-Output "$folderpath deleted!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor DarkMagenta + } +} +Function validatedest { + [CmdletBinding()] + Param( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] $sourcefolder, + + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] $destfolder, + + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] $duplicatesfolder + + ) + if(-Not(Test-Path $sourcefolder)) { + Write-Output "Fatal: Source folder ""$sourcefolder"" does not exist!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red + exit 1 + } + if(-Not(Test-Path $destfolder)) { + Write-Output "Fatal: Destination folder ""$destfolder"" does not exist!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red + exit 1 + } + if(-Not(Test-Path $duplicatesfolder)) { + Write-Output "Fatal: Duplicates folder ""$duplicatesfolder"" does not exist!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red + exit 1 + } + do { + $temp = $sourcefolder + $sourcefolder = $temp.TrimEnd('\').TrimEnd('/') + } while ($temp -ne $sourcefolder) + do { + $temp = $destfolder + $destfolder = $temp.TrimEnd('\').TrimEnd('/') + } while ($temp -ne $destfolder) + do { + $temp = $duplicatesfolder + $duplicatesfolder = $temp.TrimEnd('\').TrimEnd('/') + } while ($temp -ne $duplicatesfolder) + $sourcedir = Get-Item $sourcefolder + $destdir = Get-Item $destfolder + $duplicatesdir = Get-Item $duplicatesfolder + if ($sourcedir.FullName -eq $destdir.FullName) { + Write-Output "Fatal: Destination folder must not be the same as the source folder!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red + exit 1 + } + if ($sourcedir.FullName -eq $duplicatesdir.FullName) { + Write-Output "Fatal: Duplicates folder must not be the same as the source folder!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red + exit 1 + } + if ($duplicatesdir.FullName -eq $destdir.FullName) { + Write-Output "Fatal: Destination folder must not be the same as the duplicates folder!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red + exit 1 + } + $folderlist = @(Get-ChildItem -Path $sourcedir -Directory -Recurse) + ForEach ($folder in $folderlist) { + if ($folder.FullName -eq $destdir.FullName) { + Write-Output "Fatal: Destination folder must not be a subfolder of the source folder!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red + exit 1 + } + if ($folder.FullName -eq $duplicatesdir.FullName) { + Write-Output "Fatal: Duplicates folder must not be a subfolder of the source folder!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red + exit 1 + } + } + $folderlist = @(Get-ChildItem -Path $destdir -Directory -Recurse) + ForEach ($folder in $folderlist) { + if ($folder.FullName -eq $sourcedir.FullName) { + Write-Output "Fatal: Source folder must not be a subfolder of the destination folder!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red + exit 1 + } + if ($folder.FullName -eq $duplicatesdir.FullName) { + Write-Output "Fatal: Duplicates folder must not be a subfolder of the destination folder!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red + exit 1 + } + } + $folderlist = @(Get-ChildItem -Path $duplicatesdir -Directory -Recurse) + ForEach ($folder in $folderlist) { + if ($folder.FullName -eq $sourcedir.FullName) { + Write-Output "Fatal: Source folder must not be a subfolder of the duplicates folder!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red + exit 1 + } + if ($folder.FullName -eq $destdir.FullName) { + Write-Output "Fatal: Destination folder must not be a subfolder of the duplicates folder!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red + exit 1 + } + } +} +Function movetosubfolder +{ + [CmdletBinding()] + Param( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] $mediafile, + + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] $datestr, + + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] $sourcefolder, + + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] $destfolder, + + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] $duplicates + ) + $sourceitem="$sourcefolder/$mediafile" + # 4 = length of extension + minimum length of filename = 8+4+1 + if ($mediafile.Length -le 4) { + Write-Output "Skipped ""$sourceitem"" : File name too short!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor DarkYellow + } + else { + $stryear=$datestr.substring(0,4) + $strmonth=$datestr.substring(4,2) + $strday=$datestr.substring(6,2) + $year=[int]$stryear + $month=[int]$strmonth + $day=[int]$strday + if ($year -lt 1 -or $month -lt 1 -or $day -lt 1) { + Write-Output "Skipped ""$sourceitem"" : File ExifData has invalid date!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor DarkYellow + } + else { + $mediadatestr = "$stryear-$strmonth-$strday" + $mediadate = [DateTime]"$mediadatestr" + $mediasubfolder = "$stryear`_$strmonth`_$strday" + $destdir="$destfolder\$mediasubfolder" + $dupldir="$duplicates\$mediasubfolder" + $todaystr=Get-Date -UFormat "%Y-%m-%d" + $todaydate = [DateTime]"$todaystr" + if ($mediadate -gt $todaydate) { + Write-Output "Skipped ""$sourceitem"" : File ExifData has future date!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor DarkYellow + } + else { + $destitem="$destdir/$mediafile" + $duplitem="$dupldir/$mediafile" + if (-Not(Test-Path -Path "$destdir")) { + Write-Output "$destdir" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Green + New-Item -ItemType "Directory" -Path "$destdir" + } + if(Test-Path $destitem) { + if(Test-Path $duplitem) { + Write-Output "Skipped ""$sourceitem"" : File exists in duplicates subfolder ""$mediasubfolder""!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Yellow + } + else { + if (-Not(Test-Path -Path "$dupldir")) { + Write-Output "$dupldir" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Green + New-Item -ItemType "Directory" -Path "$dupldir" + } + Move-Item -Path "$sourceitem" -Destination "$dupldir/" + Write-Output """$sourceitem"" moved to duplicates subfolder ""$mediasubfolder""!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor DarkYellow + } + } + else { + Move-Item -Path "$sourceitem" -Destination "$destdir/" + Write-Output """$sourceitem"" moved to destination subfolder ""$mediasubfolder""!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Green + } + } + } + } +} + + +################################### +# we call main proc from here +& $Main @Args \ No newline at end of file