[CmdletBinding()] param( [switch]$DebugMode ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' function Get-YoungestTimestamp { param( [Parameter(Mandatory)] [string]$FolderPath ) $files = Get-ChildItem -LiteralPath $FolderPath -Recurse -File -ErrorAction SilentlyContinue | Where-Object { $_.FullName -notlike '*\_archive\*' } $young = $files | Sort-Object LastWriteTime -Descending | Select-Object -First 1 -ExpandProperty LastWriteTime if (-not $young) { return [datetime]::Parse('1900-01-01') } return $young } 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 '(? '$newName'" | Add-Content -LiteralPath $LogFile } continue } 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 } } try { $root = (Get-Location).ProviderPath $currentDir = Join-Path $root '_CURRENT' if (-not (Test-Path -LiteralPath $currentDir)) { New-Item -ItemType Directory -Path $currentDir -Force | Out-Null } $logFile = Join-Path $currentDir '_UpdateSequences.log' "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] === UpdateSequences (ps1) started in '$root' ===" | Add-Content -LiteralPath $logFile # Scan for folders with YYYY-MM-DD format (e.g., 2023-12-15) instead of daily_* $dailyDirs = Get-ChildItem -LiteralPath $root -Directory -Filter '????-??-??' | Where-Object { $_.Name -ne '_archive' } $mapDaily = @{} $dailiesScanned = 0 $filesRenamedTotal = 0 $renameCollisions = 0 $renameErrors = 0 <<<<<<< HEAD ======= $mp4RenamedTotal = 0 $mp4Collisions = 0 $mp4Errors = 0 >>>>>>> main foreach ($d in $dailyDirs) { $dailiesScanned++ $seqDirs = @(Get-ChildItem -LiteralPath $d.FullName -Directory | Where-Object { $_.Name -ne '_archive' }) if ($seqDirs.Count -eq 0) { $seqDirs = @($d) } foreach ($s in $seqDirs) { if (-not (Test-Path -LiteralPath $s.FullName)) { continue } $renameResult = Sync-SequenceFilenames -SequenceFolderPath $s.FullName -SequenceName $s.Name -LogFile $logFile if ($DebugMode -or $renameResult.Renamed -gt 0 -or $renameResult.Collisions -gt 0 -or $renameResult.Errors -gt 0) { Write-Host "[RENAME]|$($s.FullName)|$($s.Name)|checked=$($renameResult.Checked)|renamed=$($renameResult.Renamed)|collisions=$($renameResult.Collisions)|errors=$($renameResult.Errors)" -ForegroundColor Cyan } $filesRenamedTotal += $renameResult.Renamed $renameCollisions += $renameResult.Collisions $renameErrors += $renameResult.Errors <<<<<<< HEAD ======= if ($renameResult.Renamed -gt 0 -and $renameResult.FrameCount -gt 0 -and $null -ne $renameResult.MinFrame -and $null -ne $renameResult.MaxFrame) { $mp4Result = Rename-SequencePreviewMp4 -SequenceFolderPath $s.FullName -SequenceName $s.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]|$($s.FullName)|$($s.Name)|renamed=$($mp4Result.Renamed)|collisions=$($mp4Result.Collisions)|errors=$($mp4Result.Errors)" -ForegroundColor Cyan } $mp4RenamedTotal += $mp4Result.Renamed $mp4Collisions += $mp4Result.Collisions $mp4Errors += $mp4Result.Errors } >>>>>>> main $young = Get-YoungestTimestamp -FolderPath $s.FullName $filesCount = @(Get-ChildItem -LiteralPath $s.FullName -Recurse -File -ErrorAction SilentlyContinue | Where-Object { $_.FullName -notlike '*\_archive\*' }).Count $seqName = $s.Name $cand = [pscustomobject]@{ Seq = $seqName SrcFull = $s.FullName Young = $young FilesCount = $filesCount } if ($mapDaily.ContainsKey($seqName)) { if ($mapDaily[$seqName].Young -lt $young) { $mapDaily[$seqName] = $cand } } else { $mapDaily[$seqName] = $cand } } } $mapCurrent = @{} if (Test-Path -LiteralPath $currentDir) { foreach ($c in Get-ChildItem -LiteralPath $currentDir -Directory | Where-Object { $_.Name -ne '_archive' }) { $mapCurrent[$c.Name] = Get-YoungestTimestamp -FolderPath $c.FullName } } $lines = @() $lines += "[MAPCOUNT]|count=$($mapDaily.Count)" $total = 0 $toCopy = 0 foreach ($k in $mapDaily.Keys) { $total++ $d = $mapDaily[$k] $curYoung = if ($mapCurrent.ContainsKey($k)) { $mapCurrent[$k] } else { [datetime]::Parse('1900-01-01') } if ($d.Young -gt $curYoung) { $toCopy++ $lines += "[COPY]|$($d.SrcFull)|$($d.Seq)|files=$($d.FilesCount)|young=$($d.Young.ToString('s'))" } else { $lines += "[SKIP]|$($d.SrcFull)|$($d.Seq)|reason=notNewer|srcYoung=$($d.Young.ToString('s'))|curYoung=$($curYoung.ToString('s'))" } } $lines += "[META]|dailiesScanned=$dailiesScanned|sequencesTotal=$total|toCopy=$toCopy" # Print plan to console instead of writing to file Write-Host "=== UPDATE PLAN ===" -ForegroundColor Cyan foreach ($line in $lines) { if ($line -like '[COPY]*') { Write-Host $line -ForegroundColor Green } elseif ($line -like '[SKIP]*') { Write-Host $line -ForegroundColor Yellow } else { Write-Host $line -ForegroundColor White } } Write-Host "==================" -ForegroundColor Cyan $sequencesMirrored = 0 $mirrorFailures = 0 foreach ($line in $lines) { if ($line.StartsWith('[COPY]')) { $parts = $line -split '\|' $srcFull = $parts[1] $seqName = $parts[2] $dstAbs = Join-Path $currentDir $seqName if (Test-Path -LiteralPath $dstAbs) { "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] WIPE: removing existing destination '$dstAbs'" | Add-Content -LiteralPath $logFile try { Remove-Item -LiteralPath $dstAbs -Recurse -Force -ErrorAction Stop } catch { "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] WIPE ERROR: $($_.Exception.Message)" | Add-Content -LiteralPath $logFile } } if (-not (Test-Path -LiteralPath $dstAbs)) { New-Item -ItemType Directory -Path $dstAbs -Force | Out-Null } "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] Mirror: '$srcFull' -> '$dstAbs'" | Add-Content -LiteralPath $logFile $robocopyArgs = @( $srcFull, $dstAbs, '/MIR','/MT:8','/R:1','/W:1','/COPY:DAT','/DCOPY:DAT','/FFT','/NFL','/NDL','/NP','/NJH','/NJS','/XD','_archive' ) $null = & robocopy @robocopyArgs 2>&1 | Add-Content -LiteralPath $logFile $rc = $LASTEXITCODE if ($rc -lt 8) { $sequencesMirrored++ "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] OK rc=$rc" | Add-Content -LiteralPath $logFile } else { $mirrorFailures++ "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] ERROR rc=$rc" | Add-Content -LiteralPath $logFile } } } # Print summary report to console Write-Host "=== SUMMARY REPORT ===" -ForegroundColor Magenta Write-Host "Dailies scanned: $dailiesScanned" -ForegroundColor White Write-Host "Sequences found: $total" -ForegroundColor White Write-Host "Planned copies: $toCopy" -ForegroundColor Green Write-Host "Completed OK: $sequencesMirrored" -ForegroundColor Green Write-Host "Completed FAIL: $mirrorFailures" -ForegroundColor Red Write-Host "Files renamed: $filesRenamedTotal (collisions: $renameCollisions, errors: $renameErrors)" -ForegroundColor White Write-Host "Preview MP4s renamed: $mp4RenamedTotal (collisions: $mp4Collisions, errors: $mp4Errors)" -ForegroundColor White # Summarize skipped sequences $skippedLines = @() foreach ($line in $lines) { if ($line.StartsWith('[SKIP]')) { $skippedLines += $line } } if ($skippedLines.Count -gt 0) { Write-Host "`n=== SKIPPED SEQUENCES ===" -ForegroundColor Yellow $skippedByReason = @{} foreach ($skip in $skippedLines) { $parts = $skip -split '\|' $reason = $parts[3] $seqName = $parts[2] if (-not $skippedByReason.ContainsKey($reason)) { $skippedByReason[$reason] = @() } $skippedByReason[$reason] += $seqName } foreach ($reason in $skippedByReason.Keys) { $seqs = $skippedByReason[$reason] Write-Host "$reason ($($seqs.Count) sequences):" -ForegroundColor Yellow foreach ($seq in $seqs | Sort-Object) { Write-Host " - $seq" -ForegroundColor White } } Write-Host "========================" -ForegroundColor Yellow } Write-Host "=====================" -ForegroundColor Magenta "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] === UpdateSequences (ps1) completed (d=$dailiesScanned seq=$total ok=$sequencesMirrored fail=$mirrorFailures) ===" | Add-Content -LiteralPath $logFile Write-Host "@$logFile" exit 0 } catch { $root = (Get-Location).ProviderPath $currentDir = Join-Path $root '_CURRENT' if (-not (Test-Path -LiteralPath $currentDir)) { New-Item -ItemType Directory -Path $currentDir -Force | Out-Null } $planFile = Join-Path $currentDir '_UpdatePlan.txt' ($_.Exception | Out-String) | Set-Content -LiteralPath ($planFile + '.error.txt') -Encoding ASCII Write-Host "ERROR: $_" exit 1 }