[CmdletBinding()] param( [switch]$DebugMode ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' function Sync-SequenceFilenames { param( [Parameter(Mandatory)] [string]$SequenceFolderPath, [Parameter(Mandatory)] [string]$SequenceName, [string]$LogFile, [string[]]$Extensions = @('.png','.jpg','.jpeg','.exr','.tif','.tiff','.bmp','.tga') ) $renamed = 0 $collisions = 0 $errors = 0 $checked = 0 $minFrame = [int]::MaxValue $maxFrame = -1 $frameCount = 0 if ($LogFile) { "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] RENAME CHECK in '$SequenceFolderPath' (seq='$SequenceName')" | Add-Content -LiteralPath $LogFile } $files = Get-ChildItem -LiteralPath $SequenceFolderPath -File -Recurse -ErrorAction SilentlyContinue | Where-Object { $_.FullName -notlike '*\_archive\*' -and ($Extensions -contains $_.Extension.ToLower()) } foreach ($f in $files) { $checked++ $base = [System.IO.Path]::GetFileNameWithoutExtension($f.Name) $ext = $f.Extension $digits = $null if ($base -match '_(\d{6})$') { $digits = $Matches[1] } elseif ($base -match '(? '$archivePath'" | Add-Content -LiteralPath $LogFile } } Rename-Item -LiteralPath $f.FullName -NewName $newName -ErrorAction Stop $renamed++ if ($LogFile) { "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] RENAME: '$($f.Name)' -> '$newName'" | Add-Content -LiteralPath $LogFile } } catch { $errors++ if ($LogFile) { "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] RENAME ERROR for '$($f.Name)': $($_.Exception.Message)" | Add-Content -LiteralPath $LogFile } } } $minOut = $null $maxOut = $null if ($frameCount -gt 0) { $minOut = $minFrame $maxOut = $maxFrame } return [pscustomobject]@{ Renamed = $renamed Collisions = $collisions Errors = $errors Checked = $checked MinFrame = $minOut MaxFrame = $maxOut FrameCount = $frameCount } } function Rename-SequencePreviewMp4 { param( [Parameter(Mandatory)] [string]$SequenceFolderPath, [Parameter(Mandatory)] [string]$SequenceName, [Parameter(Mandatory)] [int]$StartFrame, [Parameter(Mandatory)] [int]$EndFrame, [string]$LogFile ) $renamed = 0 $collisions = 0 $errors = 0 $checked = 0 $targetName = "$SequenceName-$StartFrame-$EndFrame.mp4" $targetPath = Join-Path $SequenceFolderPath $targetName $mp4s = Get-ChildItem -LiteralPath $SequenceFolderPath -File -Filter '*.mp4' -ErrorAction SilentlyContinue | Where-Object { $_.FullName -notlike '*\_archive\*' } foreach ($m in $mp4s) { $checked++ if ($m.Name -eq $targetName) { continue } try { if (Test-Path -LiteralPath $targetPath) { $collisions++ if ($LogFile) { "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] MP4 RENAME SKIP collision: '$($m.Name)' -> '$targetName'" | Add-Content -LiteralPath $LogFile } continue } Rename-Item -LiteralPath $m.FullName -NewName $targetName -ErrorAction Stop $renamed++ if ($LogFile) { "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] MP4 RENAME: '$($m.Name)' -> '$targetName'" | Add-Content -LiteralPath $LogFile } break } catch { $errors++ if ($LogFile) { "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] MP4 RENAME ERROR for '$($m.Name)': $($_.Exception.Message)" | Add-Content -LiteralPath $LogFile } } } return [pscustomobject]@{ Renamed = $renamed Collisions = $collisions Errors = $errors Checked = $checked } } function Resolve-SequenceName { param( [Parameter(Mandatory)] [System.IO.DirectoryInfo]$Directory ) if ($Directory.Name -eq '_CURRENT' -and $Directory.Parent) { return $Directory.Parent.Name } return $Directory.Name } function Add-SequenceFolder { param( [Parameter(Mandatory)] [System.IO.DirectoryInfo]$Directory, [Parameter(Mandatory)] [hashtable]$Map ) if ($Directory.Name -eq '_archive') { return } $fullPath = $Directory.FullName if ($Map.ContainsKey($fullPath)) { return } $Map[$fullPath] = Resolve-SequenceName -Directory $Directory } try { $root = (Get-Location).ProviderPath $logFile = $null if ($DebugMode) { $logFile = Join-Path $root ("UpdateSequences_{0}.log" -f (Get-Date -Format 'yyyyMMdd_HHmmss')) "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] === UpdateSequences started in '$root' ===" | Out-File -LiteralPath $logFile -Encoding UTF8 } $sequenceMap = @{} $dailyDirs = Get-ChildItem -LiteralPath $root -Directory -Filter 'daily_*' -ErrorAction SilentlyContinue | Where-Object { $_.Name -ne '_archive' } foreach ($d in $dailyDirs) { $seqDirs = @(Get-ChildItem -LiteralPath $d.FullName -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -ne '_archive' }) if ($seqDirs.Count -eq 0) { Add-SequenceFolder -Directory $d -Map $sequenceMap } else { foreach ($s in $seqDirs) { Add-SequenceFolder -Directory $s -Map $sequenceMap } } } # Scan for YYYY-MM-DD format folders (home convention) $dailyDirsHome = Get-ChildItem -LiteralPath $root -Directory -Filter '????-??-??' -ErrorAction SilentlyContinue | Where-Object { $_.Name -ne '_archive' } foreach ($d in $dailyDirsHome) { $seqDirs = @(Get-ChildItem -LiteralPath $d.FullName -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -ne '_archive' }) if ($seqDirs.Count -eq 0) { Add-SequenceFolder -Directory $d -Map $sequenceMap } else { foreach ($s in $seqDirs) { Add-SequenceFolder -Directory $s -Map $sequenceMap } } } # Scan for direct sequence folders (not in daily_* or YYYY-MM-DD folders) $directSeqs = Get-ChildItem -LiteralPath $root -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -ne '_archive' -and $_.Name -notlike 'daily_*' -and $_.Name -notmatch '^\d{4}-\d{2}-\d{2}$' } foreach ($seq in $directSeqs) { Add-SequenceFolder -Directory $seq -Map $sequenceMap } $sequenceFolders = $sequenceMap.GetEnumerator() | ForEach-Object { [pscustomobject]@{ Path = $_.Key Name = $_.Value } } | Sort-Object -Property Path if (-not $sequenceFolders) { Write-Host "No sequence folders found." -ForegroundColor Yellow if ($logFile) { "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] No sequence folders found." | Add-Content -LiteralPath $logFile } exit 0 } $totalSequences = 0 $filesRenamedTotal = 0 $renameCollisions = 0 $renameErrors = 0 $mp4RenamedTotal = 0 $mp4Collisions = 0 $mp4Errors = 0 foreach ($seq in $sequenceFolders) { $totalSequences++ $renameResult = Sync-SequenceFilenames -SequenceFolderPath $seq.Path -SequenceName $seq.Name -LogFile $logFile if ($DebugMode -or $renameResult.Renamed -gt 0 -or $renameResult.Collisions -gt 0 -or $renameResult.Errors -gt 0) { Write-Host "[RENAME]|$($seq.Path)|$($seq.Name)|checked=$($renameResult.Checked)|renamed=$($renameResult.Renamed)|collisions=$($renameResult.Collisions)|errors=$($renameResult.Errors)" -ForegroundColor Cyan } $filesRenamedTotal += $renameResult.Renamed $renameCollisions += $renameResult.Collisions $renameErrors += $renameResult.Errors if ($renameResult.FrameCount -gt 0 -and $null -ne $renameResult.MinFrame -and $null -ne $renameResult.MaxFrame) { $mp4Result = Rename-SequencePreviewMp4 -SequenceFolderPath $seq.Path -SequenceName $seq.Name -StartFrame $renameResult.MinFrame -EndFrame $renameResult.MaxFrame -LogFile $logFile if ($DebugMode -or $mp4Result.Renamed -gt 0 -or $mp4Result.Collisions -gt 0 -or $mp4Result.Errors -gt 0) { Write-Host "[MP4]|$($seq.Path)|$($seq.Name)|renamed=$($mp4Result.Renamed)|collisions=$($mp4Result.Collisions)|errors=$($mp4Result.Errors)" -ForegroundColor Cyan } $mp4RenamedTotal += $mp4Result.Renamed $mp4Collisions += $mp4Result.Collisions $mp4Errors += $mp4Result.Errors } } Write-Host "=== SUMMARY REPORT ===" -ForegroundColor Magenta Write-Host "Sequences scanned: $totalSequences" -ForegroundColor White Write-Host "Files renamed: $filesRenamedTotal (collisions: $renameCollisions, errors: $renameErrors)" -ForegroundColor White Write-Host "Preview MP4s renamed: $mp4RenamedTotal (collisions: $mp4Collisions, errors: $mp4Errors)" -ForegroundColor White Write-Host "=====================" -ForegroundColor Magenta if ($logFile) { "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] === UpdateSequences completed (seq=$totalSequences renamed=$filesRenamedTotal mp4=$mp4RenamedTotal) ===" | Add-Content -LiteralPath $logFile } exit 0 } catch { if ($logFile) { "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] ERROR: $($_.Exception.Message)" | Add-Content -LiteralPath $logFile } Write-Host "ERROR: $_" -ForegroundColor Red exit 1 }