From 1a9f7293d39b2a830d0ee543138daf6e78f2433c Mon Sep 17 00:00:00 2001 From: Nick Domako Date: Fri, 27 Mar 2026 14:44:23 -0400 Subject: [PATCH] feat(photo-sorting): sort renamed photos into configurable date subfolders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After renaming, Move-Item places each photo into //, where is derived from the EXIF timestamp using a DateTime.ToString() format string. Default: "yyyy/yyMM" → 2026/2603. - SortFolderFormat key added to settings.example.json (top-level) - Collision check updated to target the subfolder path - ShouldProcess action text includes the destination folder - rename-log.json gains a DestinationPath field closes #2 --- config/settings.example.json | 3 ++- scripts/Rename-Photos.ps1 | 24 +++++++++++++++++------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/config/settings.example.json b/config/settings.example.json index 0d710a3..0848b9d 100644 --- a/config/settings.example.json +++ b/config/settings.example.json @@ -12,5 +12,6 @@ "MaxTripMiles": 250, "RoadFactor": 1.25, "TolerancePct": 0.20, - "DuplicateWindowSeconds": 120 + "DuplicateWindowSeconds": 120, + "SortFolderFormat": "yyyy/yyMM" } diff --git a/scripts/Rename-Photos.ps1 b/scripts/Rename-Photos.ps1 index e4c2d89..3e9bd3c 100644 --- a/scripts/Rename-Photos.ps1 +++ b/scripts/Rename-Photos.ps1 @@ -202,6 +202,10 @@ $fallbackLocation = if ($settings.ContainsKey('FallbackLocation') -and $settings $settings['FallbackLocation'] } else { "Unknown" } +$sortFolderFormat = if ($settings.ContainsKey('SortFolderFormat') -and $settings['SortFolderFormat']) { + $settings['SortFolderFormat'] +} else { "yyyy/yyMM" } + $configErrors = @() if (-not $Folder) { $configErrors += "settings.json: 'Paths.Source' (or 'Folder') is required" } elseif (-not (Test-Path $Folder)) { $configErrors += "Source folder not found: $Folder" } @@ -298,7 +302,8 @@ foreach ($file in $photos) { $dd = $matches[3] $hh = $matches[4] $mi = $matches[5] - $datePart = "$yy$mm$dd-$hh$mi" # e.g. "260301-1432" + $datePart = "$yy$mm$dd-$hh$mi" # e.g. "260301-1432" + $photoDate = [DateTime]::new([int]$matches[1], [int]$mm, [int]$dd, [int]$hh, [int]$mi, 0) } else { Write-Warning " Could not parse date from '$dateTimeRaw' - skipping $($file.Name)" @@ -366,9 +371,10 @@ foreach ($file in $photos) { continue } - # --- Build new filename --------------------------------------------- - $newName = "$datePart $locationName $($ocr.Reading).jpg" - $newPath = Join-Path $outputFolder $newName + # --- Build new filename and destination subfolder ------------------- + $newName = "$datePart $locationName $($ocr.Reading).jpg" + $destFolder = Join-Path $outputFolder $photoDate.ToString($sortFolderFormat, [CultureInfo]::InvariantCulture) + $newPath = Join-Path $destFolder $newName # Skip rather than overwrite an existing file if (Test-Path $newPath) { @@ -377,15 +383,19 @@ foreach ($file in $photos) { continue } - # --- Rename and log ------------------------------------------------- - if ($PSCmdlet.ShouldProcess($file.FullName, "Rename to $newName")) { + # --- Rename, sort, and log ------------------------------------------ + if ($PSCmdlet.ShouldProcess($file.FullName, "Rename to $newName and move to $destFolder")) { + $renamedPath = Join-Path (Split-Path $file.FullName -Parent) $newName Rename-Item -Path $file.FullName -NewName $newName - Write-Information " Renamed -> $newName" -InformationAction Continue + if (-not (Test-Path $destFolder)) { New-Item -ItemType Directory -Path $destFolder | Out-Null } + Move-Item -Path $renamedPath -Destination $destFolder + Write-Information " Renamed and moved -> $newPath" -InformationAction Continue $renamedCount++ $logEntries += [PSCustomObject]@{ OriginalFile = $file.Name NewFile = $newName + DestinationPath = $newPath DateTimeOriginal = $dateTimeRaw Location = $locationName Odometer = $ocr.Reading