374 lines
15 KiB
PowerShell
374 lines
15 KiB
PowerShell
[CmdletBinding()]
|
|
param(
|
|
[switch]$DebugMode
|
|
)
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
$ScriptDir = if ($PSScriptRoot) { $PSScriptRoot } else { Split-Path -Parent $MyInvocation.MyCommand.Path }
|
|
|
|
$cfgDeploy = Join-Path -Path $ScriptDir -ChildPath 'CfgDeploy.ps1'
|
|
if (-not (Test-Path -LiteralPath $cfgDeploy)) {
|
|
throw "Missing CfgDeploy.ps1 in $ScriptDir"
|
|
}
|
|
. $cfgDeploy
|
|
|
|
$useIsoDailyFormat = Use-IsoDailyFormat
|
|
|
|
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 '(?<!_)\b(\d{6})$') {
|
|
$digits = $Matches[1]
|
|
}
|
|
elseif ($base -match '(\d{4})$') {
|
|
$digits = ('00' + $Matches[1])
|
|
}
|
|
else {
|
|
continue
|
|
}
|
|
|
|
try {
|
|
$n = [int]$digits
|
|
if ($n -lt $minFrame) { $minFrame = $n }
|
|
if ($n -gt $maxFrame) { $maxFrame = $n }
|
|
$frameCount++
|
|
} catch {}
|
|
|
|
$targetBase = "$SequenceName" + '_' + $digits
|
|
if ($base -eq $targetBase) { continue }
|
|
|
|
$newName = $targetBase + $ext
|
|
$newPath = Join-Path $SequenceFolderPath $newName
|
|
try {
|
|
if (Test-Path -LiteralPath $newPath) {
|
|
$existing = Get-Item -LiteralPath $newPath -ErrorAction Stop
|
|
$sameSize = ($existing.Length -eq $f.Length)
|
|
$sameTime = ([math]::Abs(($existing.LastWriteTimeUtc - $f.LastWriteTimeUtc).TotalSeconds) -le 1)
|
|
if ($sameSize -and $sameTime) {
|
|
Remove-Item -LiteralPath $f.FullName -Force -ErrorAction Stop
|
|
$collisions++
|
|
if ($LogFile) { "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] RENAME DROP duplicate: '$($f.Name)' (matches '$newName')" | Add-Content -LiteralPath $LogFile }
|
|
continue
|
|
}
|
|
|
|
$archiveDir = Join-Path $SequenceFolderPath '_archive'
|
|
if (-not (Test-Path -LiteralPath $archiveDir)) {
|
|
New-Item -ItemType Directory -Path $archiveDir -Force | Out-Null
|
|
}
|
|
|
|
$archiveName = $existing.Name
|
|
$archivePath = Join-Path $archiveDir $archiveName
|
|
if (Test-Path -LiteralPath $archivePath) {
|
|
$stamp = Get-Date -Format 'yyyyMMdd_HHmmss'
|
|
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($archiveName)
|
|
$archivePath = Join-Path $archiveDir ("{0}_{1}{2}" -f $baseName, $stamp, $existing.Extension)
|
|
}
|
|
|
|
Move-Item -LiteralPath $existing.FullName -Destination $archivePath -Force -ErrorAction Stop
|
|
$collisions++
|
|
if ($LogFile) { "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] RENAME ARCHIVE existing '$($existing.Name)' -> '$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 Get-Mp4FrameRange {
|
|
param([string]$Name)
|
|
if ($Name -match '[-_](\d+)[-_](\d+)\.mp4$') {
|
|
return [pscustomobject]@{ Start = [int]$Matches[1]; End = [int]$Matches[2] }
|
|
}
|
|
return $null
|
|
}
|
|
|
|
function Rename-SequencePreviewMp4 {
|
|
param(
|
|
[Parameter(Mandatory)] [string]$SequenceFolderPath,
|
|
[Parameter(Mandatory)] [string]$SequenceName,
|
|
[Parameter(Mandatory)] [int]$StartFrame,
|
|
[Parameter(Mandatory)] [int]$EndFrame,
|
|
[Parameter(Mandatory)] [string]$RootPath,
|
|
[string]$LogFile
|
|
)
|
|
$renamed = 0
|
|
$collisions = 0
|
|
$errors = 0
|
|
$checked = 0
|
|
|
|
$targetName = "$SequenceName-$StartFrame-$EndFrame.mp4"
|
|
$targetPath = Join-Path $SequenceFolderPath $targetName
|
|
$collisionsDir = Join-Path $RootPath '_collisions'
|
|
|
|
$mp4s = @(Get-ChildItem -LiteralPath $SequenceFolderPath -File -Filter '*.mp4' -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.FullName -notlike '*\_archive\*' })
|
|
|
|
if ($mp4s.Count -eq 0) { return [pscustomobject]@{ Renamed = 0; Collisions = 0; Errors = 0; Checked = 0 } }
|
|
|
|
$checked = $mp4s.Count
|
|
|
|
$candidates = @($mp4s | ForEach-Object {
|
|
$fr = Get-Mp4FrameRange -Name $_.Name
|
|
$frameMatch = ($fr -and $fr.Start -eq $StartFrame -and $fr.End -eq $EndFrame)
|
|
[pscustomobject]@{
|
|
File = $_
|
|
FrameMatch = $frameMatch
|
|
LastWrite = $_.LastWriteTimeUtc
|
|
}
|
|
} | Sort-Object -Property @{ Expression = { -not $_.FrameMatch }; Ascending = $true }, @{ Expression = 'LastWrite'; Descending = $true })
|
|
|
|
$best = $candidates[0].File
|
|
$moveToCollisions = @(if ($candidates.Count -gt 1) { $candidates[1..($candidates.Count - 1)] } else { @() })
|
|
|
|
$hasCollisions = ($moveToCollisions.Count -gt 0) -or ($best.Name -ne $targetName -and (Test-Path -LiteralPath $targetPath))
|
|
if (-not $hasCollisions -and $best.Name -eq $targetName) {
|
|
return [pscustomobject]@{ Renamed = 0; Collisions = 0; Errors = 0; Checked = $checked }
|
|
}
|
|
|
|
try {
|
|
if ($hasCollisions -and -not (Test-Path -LiteralPath $collisionsDir)) {
|
|
New-Item -ItemType Directory -Path $collisionsDir -Force | Out-Null
|
|
}
|
|
|
|
foreach ($c in $moveToCollisions) {
|
|
$f = $c.File
|
|
$destName = $f.Name
|
|
$destPath = Join-Path $collisionsDir $destName
|
|
if (Test-Path -LiteralPath $destPath) {
|
|
$base = [System.IO.Path]::GetFileNameWithoutExtension($destName)
|
|
$destName = "{0}_{1}.mp4" -f $base, (Get-Date -Format 'yyyyMMdd_HHmmss')
|
|
$destPath = Join-Path $collisionsDir $destName
|
|
}
|
|
Move-Item -LiteralPath $f.FullName -Destination $destPath -Force -ErrorAction Stop
|
|
$collisions++
|
|
if ($LogFile) { "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] MP4 COLLISION moved: '$($f.Name)' -> '$destPath'" | Add-Content -LiteralPath $LogFile }
|
|
}
|
|
|
|
if ($best.Name -ne $targetName) {
|
|
if (Test-Path -LiteralPath $targetPath) {
|
|
$destPath = Join-Path $collisionsDir $targetName
|
|
if (Test-Path -LiteralPath $destPath) {
|
|
$base = [System.IO.Path]::GetFileNameWithoutExtension($targetName)
|
|
$destPath = Join-Path $collisionsDir ("{0}_{1}.mp4" -f $base, (Get-Date -Format 'yyyyMMdd_HHmmss'))
|
|
}
|
|
Move-Item -LiteralPath $targetPath -Destination $destPath -Force -ErrorAction Stop
|
|
$collisions++
|
|
}
|
|
Rename-Item -LiteralPath $best.FullName -NewName $targetName -ErrorAction Stop
|
|
$renamed++
|
|
if ($LogFile) { "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] MP4 RENAME: '$($best.Name)' -> '$targetName'" | Add-Content -LiteralPath $LogFile }
|
|
}
|
|
} catch {
|
|
$errors++
|
|
if ($LogFile) { "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] MP4 ERROR: $($_.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 -like '_*') { return }
|
|
$fullPath = $Directory.FullName
|
|
if ($Map.ContainsKey($fullPath)) { return }
|
|
|
|
# Check if directory contains sequence files directly (not just subdirectories)
|
|
$extensions = @('.png','.jpg','.jpeg','.exr','.tif','.tiff','.bmp','.tga')
|
|
$hasSequenceFiles = Get-ChildItem -LiteralPath $fullPath -File -ErrorAction SilentlyContinue |
|
|
Where-Object { $extensions -contains $_.Extension.ToLower() } |
|
|
Select-Object -First 1
|
|
|
|
# If it only has subdirectories and no sequence files, skip it
|
|
$hasSubdirs = Get-ChildItem -LiteralPath $fullPath -Directory -ErrorAction SilentlyContinue |
|
|
Select-Object -First 1
|
|
if ($null -eq $hasSequenceFiles -and $null -ne $hasSubdirs) {
|
|
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 = @{}
|
|
|
|
$primaryPattern = if ($useIsoDailyFormat) { '????-??-??' } else { 'daily_*' }
|
|
$secondaryPattern = if ($useIsoDailyFormat) { 'daily_*' } else { '????-??-??' }
|
|
|
|
$primaryDirs = Get-ChildItem -LiteralPath $root -Directory -Filter $primaryPattern -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.Name -notlike '_*' }
|
|
foreach ($d in $primaryDirs) {
|
|
$seqDirs = @(Get-ChildItem -LiteralPath $d.FullName -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -notlike '_*' })
|
|
if ($seqDirs.Count -eq 0) {
|
|
Add-SequenceFolder -Directory $d -Map $sequenceMap
|
|
} else {
|
|
foreach ($s in $seqDirs) {
|
|
Add-SequenceFolder -Directory $s -Map $sequenceMap
|
|
}
|
|
}
|
|
}
|
|
|
|
$secondaryDirs = Get-ChildItem -LiteralPath $root -Directory -Filter $secondaryPattern -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.Name -notlike '_*' }
|
|
foreach ($d in $secondaryDirs) {
|
|
$seqDirs = @(Get-ChildItem -LiteralPath $d.FullName -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -notlike '_*' })
|
|
if ($seqDirs.Count -eq 0) {
|
|
Add-SequenceFolder -Directory $d -Map $sequenceMap
|
|
} else {
|
|
foreach ($s in $seqDirs) {
|
|
Add-SequenceFolder -Directory $s -Map $sequenceMap
|
|
}
|
|
}
|
|
}
|
|
|
|
$directSeqs = Get-ChildItem -LiteralPath $root -Directory -ErrorAction SilentlyContinue |
|
|
Where-Object {
|
|
$_.Name -notlike '_*' -and
|
|
$_.Name -notlike 'daily_*' -and
|
|
$_.Name -notmatch '^\d{4}-\d{2}-\d{2}$'
|
|
}
|
|
foreach ($seq in $directSeqs) {
|
|
$seqDirs = @(Get-ChildItem -LiteralPath $seq.FullName -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -notlike '_*' })
|
|
if ($seqDirs.Count -eq 0) {
|
|
Add-SequenceFolder -Directory $seq -Map $sequenceMap
|
|
} else {
|
|
foreach ($s in $seqDirs) {
|
|
Add-SequenceFolder -Directory $s -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 -RootPath $root -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
|
|
} |