<# .SYNOPSIS Finds and organizes likely GPT-generated images. .DESCRIPTION This script scans a selected folder and its subfolders for image files. It can: - Find likely ChatGPT, OpenAI, DALL-E, or GPT images. - Optionally include every image in the source folder. - Copy images into folders organized by year and month. - Rename the copied images consistently. - Detect duplicate files using SHA-256 hashes. - Create a CSV inventory. - Preview the operation without changing anything. Originals are never deleted or moved. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$SourceFolder, [Parameter(Mandatory = $true)] [string]$OutputFolder, [switch]$IncludeAllImages, [switch]$PreviewOnly ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" # Image formats included in the scan. $SupportedExtensions = @( ".png", ".jpg", ".jpeg", ".webp", ".gif" ) # Words that may identify an image as coming from GPT-related work. $GPTTerms = @( "chatgpt", "chat-gpt", "openai", "dall-e", "dalle", "gpt", "generated-image", "generated_image", "imagegen", "ai-generated" ) # Statistics used in the final report. $Stats = [ordered]@{ ImagesExamined = 0 GPTImagesFound = 0 ImagesCopied = 0 DuplicatesSkipped = 0 Errors = 0 } $Inventory = [System.Collections.Generic.List[object]]::new() function Get-FileSha256 { param ( [Parameter(Mandatory = $true)] [string]$Path ) return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant() } function Test-IsLikelyGPTImage { param ( [Parameter(Mandatory = $true)] [System.IO.FileInfo]$File ) $SearchText = "$($File.Name) $($File.DirectoryName)".ToLowerInvariant() foreach ($Term in $GPTTerms) { if ($SearchText.Contains($Term.ToLowerInvariant())) { return $true } } return $false } # Validate the source folder. if (-not (Test-Path -LiteralPath $SourceFolder -PathType Container)) { throw "The source folder does not exist: $SourceFolder" } $ResolvedSource = (Resolve-Path -LiteralPath $SourceFolder).Path # In preview mode, we do not need to create the output folder immediately. if (-not $PreviewOnly) { New-Item -ItemType Directory -Path $OutputFolder -Force | Out-Null } $FullOutputPath = [System.IO.Path]::GetFullPath($OutputFolder) Write-Host "" Write-Host "GPT Image Organizer" Write-Host "-------------------" Write-Host "Source: $ResolvedSource" Write-Host "Output: $FullOutputPath" Write-Host "Include all images: $IncludeAllImages" Write-Host "Preview only: $PreviewOnly" Write-Host "" # Build a set of hashes already present in the destination. # This helps avoid copying identical images more than once. $ExistingHashes = [System.Collections.Generic.HashSet[string]]::new( [System.StringComparer]::OrdinalIgnoreCase ) if (Test-Path -LiteralPath $OutputFolder -PathType Container) { $ExistingFiles = Get-ChildItem ` -LiteralPath $OutputFolder ` -File ` -Recurse ` -ErrorAction SilentlyContinue foreach ($ExistingFile in $ExistingFiles) { if ($SupportedExtensions -contains $ExistingFile.Extension.ToLowerInvariant()) { try { $ExistingHash = Get-FileSha256 -Path $ExistingFile.FullName [void]$ExistingHashes.Add($ExistingHash) } catch { Write-Warning "Could not hash existing file: $($ExistingFile.FullName)" } } } } # Find all supported image files. $ImageFiles = Get-ChildItem ` -LiteralPath $ResolvedSource ` -File ` -Recurse ` -ErrorAction SilentlyContinue | Where-Object { $SupportedExtensions -contains $_.Extension.ToLowerInvariant() } foreach ($File in $ImageFiles) { $Stats.ImagesExamined++ try { $IsGPTImage = Test-IsLikelyGPTImage -File $File if ($IsGPTImage) { $Stats.GPTImagesFound++ $Classification = "Likely GPT Image" } elseif ($IncludeAllImages) { $Classification = "Included as General Image" } else { # Skip images that are not likely GPT images. continue } $FileHash = Get-FileSha256 -Path $File.FullName $ShortHash = $FileHash.Substring(0, 10) $ModifiedDate = $File.LastWriteTime $Year = $ModifiedDate.ToString("yyyy") $Month = $ModifiedDate.ToString("MM") $DestinationDirectory = Join-Path $OutputFolder $Year $DestinationDirectory = Join-Path $DestinationDirectory $Month $Timestamp = $ModifiedDate.ToString("yyyy-MM-dd_HH-mm-ss") $Extension = $File.Extension.ToLowerInvariant() $NewName = "GPT_Image_{0}_{1}{2}" -f $Timestamp, $ShortHash, $Extension $NewPath = Join-Path $DestinationDirectory $NewName if ($ExistingHashes.Contains($FileHash)) { $Stats.DuplicatesSkipped++ $Status = "Duplicate skipped" Write-Host "[DUPLICATE] $($File.FullName)" } elseif ($PreviewOnly) { $Status = "Preview only" Write-Host "[PREVIEW] $($File.FullName)" Write-Host " -> $NewPath" } else { New-Item ` -ItemType Directory ` -Path $DestinationDirectory ` -Force | Out-Null Copy-Item ` -LiteralPath $File.FullName ` -Destination $NewPath ` -Force:$false [void]$ExistingHashes.Add($FileHash) $Stats.ImagesCopied++ $Status = "Copied" Write-Host "[COPIED] $($File.Name) -> $NewName" } $Inventory.Add( [PSCustomObject]@{ OriginalName = $File.Name OriginalPath = $File.FullName NewName = $NewName NewPath = $NewPath Extension = $Extension FileSizeBytes = $File.Length DateModified = $ModifiedDate SHA256 = $FileHash Classification = $Classification Status = $Status } ) } catch { $Stats.Errors++ Write-Warning "Could not process: $($File.FullName)" Write-Warning $_.Exception.Message $Inventory.Add( [PSCustomObject]@{ OriginalName = $File.Name OriginalPath = $File.FullName NewName = "" NewPath = "" Extension = $File.Extension FileSizeBytes = $File.Length DateModified = $File.LastWriteTime SHA256 = "" Classification = "Unknown" Status = "Error: $($_.Exception.Message)" } ) } } # Save the inventory. $InventoryTimestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss" $InventoryFileName = "GPT_Image_Inventory_$InventoryTimestamp.csv" $InventoryPath = Join-Path $OutputFolder $InventoryFileName if ($PreviewOnly) { # In preview mode, place the report in the current working directory # if the destination folder does not exist. if (-not (Test-Path -LiteralPath $OutputFolder -PathType Container)) { $InventoryPath = Join-Path (Get-Location) $InventoryFileName } } if ($Inventory.Count -gt 0) { $Inventory | Export-Csv ` -LiteralPath $InventoryPath ` -NoTypeInformation ` -Encoding UTF8 } Write-Host "" Write-Host "Completed" Write-Host "---------" Write-Host "Images examined: $($Stats.ImagesExamined)" Write-Host "GPT images found: $($Stats.GPTImagesFound)" Write-Host "Images copied: $($Stats.ImagesCopied)" Write-Host "Duplicates skipped: $($Stats.DuplicatesSkipped)" Write-Host "Errors: $($Stats.Errors)" Write-Host "Inventory CSV: $InventoryPath" Write-Host "" if ($PreviewOnly) { Write-Host "This was a preview. No images were copied." } else { Write-Host "The original images were not moved or deleted." }