Last active
May 3, 2023 02:24
-
-
Save sharpninja/c4a1e2206f8cf733097a0f5cdb0ef9bc to your computer and use it in GitHub Desktop.
Moves all videos from a source folder to a destination folder.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! pwsh | |
param ( | |
[Parameter()] | |
[string[]] | |
$SourcePaths = @($PWD), | |
[Parameter()] | |
[string] | |
$DestinationPath = $null, | |
[Parameter()] | |
[string[]] | |
$MatchPatterns = @('\.(mp4|m4v|m4p)', ` | |
'\.(mpg|mp2|mpeg|mpv|mpe|m2v)', ` | |
'\.(3gp|3g2)', '\.mkv', '\.webm', ` | |
'\.(flv|f4v|f4p|f4a|f4b)', ` | |
'\.vob', ` | |
'\.ogv', ` | |
'\.avi', ` | |
'\.(mts|m2ts)', '\.(mov|qt)', '\.wmv', '\.asf'), | |
[Parameter()] | |
[switch]$WhatIf = $false, | |
[switch]$Debugging = $false, | |
[switch]$Undo = $false | |
) | |
$SourcePathsString = $([string]::Join('`, `', $SourcePaths)); | |
if ($Undo -eq $true) { | |
$sp = $DestinationPath; | |
$DestinationPath = $SourcePaths[0]; | |
$SourcePaths = @($sp); | |
} | |
$Verbose = $true; | |
$Debug = $Debugging; | |
class Result { | |
[Match[]]$Matches; | |
[Int32]$Processed; | |
Result( | |
[Match[]]$m, | |
[Int32]$p | |
) { | |
$this.Matches = $m; | |
$this.Processed = $p; | |
} | |
} | |
class Match { | |
[string]$Pattern; | |
[string]$RelativePath; | |
[System.IO.FileInfo]$Item; | |
Match( | |
[System.IO.FileInfo]$i, | |
[string]$p, | |
[System.IO.DirectoryInfo]$s | |
) { | |
$this.Item = $i; | |
$this.Pattern = $p; | |
$this.RelativePath = $i.FullName.Replace("$($s.FullName)", '').TrimStart("\/".ToCharArray()); | |
} | |
[string]AsJson() { | |
return "{ '$($this.Pattern)': '$($this.RelativePath)' }" | |
} | |
} | |
function Write-Hashtable { | |
param( | |
[Match[]]$matched | |
) | |
foreach ($result in $matched) { | |
if ($result -is [Match[]]) { | |
Write-Matches $result; | |
} | |
else { | |
Write-Information $result.AsJson(); | |
} | |
} | |
} | |
$Source = Get-Item $SourcePaths[0] -ErrorAction SilentlyContinue; | |
if ($DestinationPath) { | |
if (-not(Test-Path $DestinationPath)) { | |
New-Item $DestinationPath -ItemType Directory | |
} | |
$Destination = Get-Item $DestinationPath -ErrorAction Stop; | |
} | |
$Patterns = $MatchPatterns; | |
Push-Location | |
Set-Location $Source | |
Write-Debug -Debug:$Debug "Debug output enabled." | |
Write-Verbose -Verbose:$Verbose "Verbose output enabled." | |
if (($Debug -eq $true) -and ($Verbose -eq $true)) { | |
Write-Verbose -Verbose:$Verbose "```$Source`` = $Source" | |
Write-Verbose -Verbose:$Verbose "```$Destination`` = $Destination" | |
Write-Verbose -Verbose:$Verbose "```$Patterns`` = [$([string]::Join('], [', $Patterns))]" | |
} | |
$activity = "Video-Consolidator"; | |
[Int32]$index = 0.0; | |
[Int32]$completed = 0; | |
[System.IO.FileSystemInfo[]]$Paths = @(); | |
foreach ($SourcePath in $SourcePaths) { | |
Set-Location $SourcePath | |
Write-Verbose -Verbose:$Verbose "Counting Children of ``$SourcePath``" | |
[System.IO.FileSystemInfo[]]$PathInfos = Get-ChildItem -Recurse -Exclude $DestinationPath -Verbose:$Verbose; | |
Write-Verbose -Verbose:$Verbose "Expecting $($PathInfos.Length) children in ``$SourcePath`` to be processed." | |
$Paths += $PathInfos; | |
} | |
[Int32]$count = $Paths.Length; | |
function Get-ByMatchPatterns { | |
[OutputType([Result])] | |
param ( | |
[Parameter()] | |
[System.IO.FileSystemInfo[]] | |
$Paths = @($Source), | |
[Int32]$StartIndex = $index, | |
[switch]$WhatIf = $false | |
) | |
try { | |
[Int32]$CurrentIndex = $StartIndex; | |
[Result]$results = [Result]::new(@(), $StartIndex); | |
$Paths | ForEach-Object -Process { | |
[System.IO.FileSystemInfo]$Path = $_; | |
[string]$status = "$($Path ?? '<<null>>')"; | |
try { | |
if ($null -ne $Path) { | |
$PathItem = $Path | |
[string]$fullName = ($PathItem?.FullName) ?? "$PathItem"; | |
[string]$relPath = $fullName.Replace("$($Source.FullName)", '').TrimStart("\/".ToCharArray()); | |
if ($PathItem -is [System.IO.DirectoryInfo]) { | |
if ($Verbose -eq $true -and ($Debug -eq $true)) { | |
$status = "[$completed % : $CurrentIndex] ``$relPath`` is a directory."; | |
Write-Debug -Debug:$Debug $status | |
} | |
} | |
elseif ($PathItem -is [System.IO.FileInfo] -and (-not $PathItem.LinkTarget)) { | |
# Is a file. | |
if ($Verbose -eq $true -and ($Debug -eq $true)) { | |
$status = "[$completed % : $CurrentIndex] ``$relPath`` is a file."; | |
Write-Debug -Debug:$Debug $status | |
} | |
foreach ($Pattern in $Patterns) { | |
$Pattern="$Pattern\b"; | |
if ($PathItem.FullName -imatch $Pattern) { | |
$results.Matches += [Match]::new($PathItem, $Pattern, $Source); | |
if ($Verbose -eq $true -and ($Debug -eq $true)) { | |
Write-Verbose -Verbose:$Verbose "[$completed % : $CurrentIndex] ``$($PathItem.FullName)`` matches on ``$Pattern``" | |
} | |
} | |
} | |
} | |
else { | |
# Other FileSystemInfo | |
if ($Verbose -eq $true -and ($Debug -eq $true)) { | |
$status = "[$completed % : $CurrentIndex] ``$relPath`` is a ``$($PathItem.GetType().Name)``."; | |
Write-Debug -Debug:$Debug $status | |
} | |
} | |
} | |
} | |
catch { | |
$status = "[$completed % : $CurrentIndex] Error: $_" | |
if ($Verbose -eq $true -and ($Debug -eq $true)) { | |
Write-Debug -Debug:$Debug $status | |
} | |
} | |
finally { | |
[Int32]$CurrentIndex = [Int32]$CurrentIndex + [Int32]1.0; | |
[Int32]$completed = ([decimal]$CurrentIndex / [decimal]$count) * [decimal]100.0; | |
$status = "Processed $CurrentIndex of $count in ``$SourcePathsString``" | |
Write-Progress -CurrentOperation "Find Matches" ` | |
-Status $status ` | |
-Activity $activity ` | |
-PercentComplete $completed; | |
} | |
} | |
$results.Processed = $CurrentIndex - $StartIndex; | |
if ($Debug -eq $true) { | |
if ($results.Matches.Length -eq 0) { | |
Write-Debug -Debug:$Debug "No matches in ``$SourcePathsString``" | |
} | |
else { | |
Write-Hashtable $results.Matches | |
} | |
} | |
if ($results -is [Result]) { | |
$results; | |
} | |
else { | |
$resultType = $result.GetType().FullName; | |
throw "Expected `$results to be [Result], but `$results is [$resultType]"; | |
} | |
} | |
finally { | |
Write-Progress -CurrentOperation "Find Matches" ` | |
-Status "Completed searching in $($PathInfos.Length) paths." ` | |
-Activity $activity ` | |
-PercentComplete 100; | |
} | |
} | |
function Move-Videos { | |
[OutputType([Match[]])] | |
param( | |
[Match[]]$AllResults = $null, | |
[switch]$WhatIf = $false | |
) | |
Write-Verbose -Verbose:$Verbose "Found $($AllResults.Length) Matches in ``$SourcePathsString``." | |
if ($Verbose -eq $true) { | |
$AllResults | Format-Table -AutoSize -GroupBy Pattern -Verbose:$Verbose | |
} | |
if ($Destination -and ($AllResults.Length -gt 0)) { | |
$SourceItems = $AllResults; | |
$CopiedFiles = @(); | |
Write-Information "Copying $($SourceItems.Length) to ``$Destination``" | |
$copied = $SourceItems | ForEach-Object -Process { | |
$toCopy = $_.RelativePath; | |
$SourcePath=$_.Item.FullName.Replace($toCopy,''); | |
$sourceFile = $_.Item.FullName; | |
$destinationFile = "$DestinationPath/$toCopy"; | |
$retryCount = 0; | |
Set-Location $SourcePath; | |
if (Test-Path $destinationFile) { | |
$existing = Get-Item $destinationFile; | |
if ($existing.LinkTarget) { | |
Remove-Item $existing -Force -Verbose:$Verbose; | |
Write-Verbose -Verbose:$Verbose "Removed existing link at $($existing.FullName)"; | |
} | |
} | |
while (Test-Path $destinationFile) { | |
$destinationFile = "$DestinationPath/$toCopy (++$retryCount)"; | |
} | |
$destinationFolder = $destinationFile.Replace((Split-Path $destinationFile -Leaf), ""); | |
Write-Verbose -Verbose:$Verbose "`$destinationFolder: $destinationFolder"; | |
if (-not(Test-Path $destinationFolder)) { | |
New-Item $destinationFolder -ItemType Directory -Verbose:$Verbose -WhatIf:$WhatIf; | |
} | |
Move-Item $sourceFile -Destination $destinationFile -Verbose:$Verbose -WhatIf:$WhatIf -ErrorAction Stop -Force; | |
$VideoPath = $sourceFile; | |
$TargetPath = $destinationFile; | |
New-Item -Path $VideoPath -ItemType SymbolicLink -Value $TargetPath -Verbose:$Verbose -WhatIf:$WhatIf | |
if ($WhatIf -ne $true) { | |
$copiedFile = Get-Item $destinationFile -Verbose:$Verbose -ErrorAction Stop; | |
if ($copiedFile) { | |
$copiedFile | |
$CopiedFiles += [Match]::new($copiedFile, $copiedFile.Extension, $copiedFile.Directory); | |
$status = "Moved ``$($copiedFile.Name)`` and created link at ``$TargetPath``" | |
Write-Progress -Activity $activity ` | |
-Status $status ` | |
-CurrentOperation "Create Links" ` | |
-Completed $completed; | |
} | |
else { | |
Push-Location; | |
Set-Location $Destination | |
foreach ($match in $CopiedFiles) { | |
$sf = Get-Item $match.Item; | |
if ($sf.Exists -and ($sf -is [System.IO.SymbolicLink])) { | |
Remove-Item $sf -Force -Verbose:$Verbose; | |
Write-Debug -Debug:$Debug "Removed link at ``$($sf.FullName)``"; | |
} | |
$sf = Get-Item $match.Item; | |
if (-not($sf.Exists)) { | |
Move-Item $match.RelativePath -Destination $sf.FullName -ErrorAction Stop -Verbose:$Verbose; | |
Write-Debug -Debug:$Debug "Moved ``$($match.RelativePath)`` back to ``$($sf.FullName)``"; | |
} | |
else { | |
Write-Debug -Debug:$Debug "Skipping ``$($sf.FullName)`` because it is still present."; | |
} | |
} | |
Pop-Location; | |
throw "``$destinationFile`` was not created and moved files were restored." | |
} | |
} | |
else { | |
Get-Item $sourceFile; | |
} | |
} | |
if (-not $copied) { | |
throw "No videos were copied."; | |
} | |
Set-Location $Destination | |
$DestinationItems = ($SourceItems.RelativePath | Get-ChildItem -ErrorAction SilentlyContinue)?.FullName | |
$lengthsMatch = ($DestinationItems.Length -eq $SourceItems.Length); | |
if (-not($WhatIf) -and $lengthsMatch) { | |
Write-Information "Linked $($SourceItems.Length) Paths to Destination Videos." | |
} | |
else { | |
throw "Expected ``$DestinationPath`` to contain $($SourceItems.Length) videos, but $($DestinationItems.Length) were found."; | |
} | |
} | |
if ($Destination) { | |
$status = "Completed Moving and Linking $($AllResults.Length) Videos from ``$SourcePathsString`` to ``$DestinationPath``"; | |
Write-Host $status | |
} | |
if ($Debug -eq $true) { | |
} | |
else { | |
$AllResults | |
} | |
} | |
try { | |
[Result]$r = (Get-ByMatchPatterns -Paths $Paths -StartIndex $index -Verbose:$Verbose -WhatIf:$WhatIf)[0]; | |
Move-Videos -AllResults $r.Matches; | |
} | |
catch { | |
$err = $_; | |
Write-Error $err; | |
Write-Error $err.ScriptStackTrace; | |
} | |
finally { | |
Write-Progress -Activity $activity ` | |
-Status "Finished" ` | |
-CurrentOperation "Create Links" ` | |
-Completed 100; | |
Pop-Location | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment