I got tired of manually looking at files in Explorer, and I don’t care to use a more significant player/library management tool to find a few problematic funscripts that are “missing” a movie or movies without a funscript. I wanted something simple that I could run per directory or recursively to help find the actual gaps or where I just need to make a rename.
Just save/rename the below as CheckFunscripts.ps1 and run in Powershell:
powershell -ExecutionPolicy Bypass -File .\CheckFunscripts.ps1
Or run recursively:
powershell -ExecutionPolicy Bypass -File .\CheckFunscripts.ps1 -Recurse
CheckFunscripts.txt (5.6 KB)
param(
[Parameter(Position=0)]
[string]$Root = (Get-Location).Path,
[switch]$Recurse,
# Add/remove extensions here (no dots)
[string[]]$MovieExts = @('mp4','mkv','avi','mov','m4v','wmv','webm','mpg','mpeg','ts','m2ts','mts','flv','3gp','vob'),
[string]$ReportPath
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
function Get-TrimmedAtLastDot {
param([string]$Name)
$idx = $Name.LastIndexOf('.')
if ($idx -lt 0) { return $null }
return $Name.Substring(0, $idx)
}
# Resolve root reliably (mapped or UNC)
try { $rootItem = Get-Item -LiteralPath $Root }
catch { throw "Root path not found or not accessible: $Root" }
$Root = $rootItem.FullName
if (-not $ReportPath) {
$ReportPath = Join-Path $Root 'funscript_audit_report.txt'
}
$gciBase = @{
LiteralPath = $Root
File = $true
}
if ($Recurse) { $gciBase.Recurse = $true }
# ---------- Enumerate movies (targeted) ----------
$movieFiles = New-Object System.Collections.Generic.List[System.IO.FileInfo]
foreach ($ext in $MovieExts) {
$pattern = "*.$ext"
foreach ($f in (Get-ChildItem @gciBase -Filter $pattern)) {
$movieFiles.Add($f)
}
}
# ---------- Enumerate funscripts (targeted) ----------
$funscriptFiles = @(Get-ChildItem @gciBase -Filter '*.funscript')
# ---------- Build movie sets ----------
$movieNameSet = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
$movieBaseToPaths = @{}
foreach ($m in $movieFiles) {
$base = [System.IO.Path]::GetFileNameWithoutExtension($m.Name) # dots preserved
[void]$movieNameSet.Add($base)
if (-not $movieBaseToPaths.ContainsKey($base)) {
$movieBaseToPaths[$base] = New-Object System.Collections.Generic.List[string]
}
$movieBaseToPaths[$base].Add($m.FullName)
}
# ---------- Build funscript key set ----------
$funscriptKeySet = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
$funscriptInfo = New-Object System.Collections.Generic.List[object]
foreach ($f in $funscriptFiles) {
$fullBase = [System.IO.Path]::GetFileNameWithoutExtension($f.Name)
$trimBase = Get-TrimmedAtLastDot $fullBase
# Add full base (supports dotted movie names)
[void]$funscriptKeySet.Add($fullBase)
# Add trimmed base (supports axis variants)
if ($trimBase -and ($trimBase -ne $fullBase)) {
[void]$funscriptKeySet.Add($trimBase)
}
$funscriptInfo.Add([pscustomobject]@{
Path = $f.FullName
FullBase = $fullBase
TrimBase = $trimBase
})
}
# ---------- Movies missing funscripts ----------
$moviesMissingFunscriptPaths = New-Object System.Collections.Generic.List[string]
foreach ($kvp in $movieBaseToPaths.GetEnumerator()) {
$movieBase = $kvp.Key
if (-not $funscriptKeySet.Contains($movieBase)) {
foreach ($p in $kvp.Value) { $moviesMissingFunscriptPaths.Add($p) }
}
}
# ---------- Funscripts missing movies ----------
$funscriptsMissingMoviePaths = New-Object System.Collections.Generic.List[string]
foreach ($fi in $funscriptInfo) {
$matched = $movieNameSet.Contains($fi.FullBase) -or ($fi.TrimBase -and $movieNameSet.Contains($fi.TrimBase))
if (-not $matched) { $funscriptsMissingMoviePaths.Add($fi.Path) }
}
# ---------- Sorting (force string arrays) ----------
$moviesMissingSorted = @($moviesMissingFunscriptPaths.ToArray() | Sort-Object)
$funscriptsMissingSorted = @($funscriptsMissingMoviePaths.ToArray() | Sort-Object)
# ---------- Summary ----------
$mode = if ($Recurse) { 'Recursive' } else { 'Non-recursive' }
$now = Get-Date
$movieFileCount = $movieFiles.Count
$funscriptFileCount = $funscriptFiles.Count
$uniqueMovieNames = $movieNameSet.Count
$uniqueFunscriptKeys = $funscriptKeySet.Count
# ---------- Write report ----------
$lines = New-Object System.Collections.Generic.List[string]
$lines.Add('============================================================')
$lines.Add('FUNSCRIPT / MOVIE AUDIT REPORT')
$lines.Add('============================================================')
$lines.Add("Generated: $now")
$lines.Add("Root: $Root")
$lines.Add("Mode: $mode")
$lines.Add("Movie exts: " + ($MovieExts -join ' '))
$lines.Add('')
$lines.Add("Movie files found: $movieFileCount")
$lines.Add("Funscript files found: $funscriptFileCount")
$lines.Add("Unique movie base names: $uniqueMovieNames")
$lines.Add("Unique funscript match keys: $uniqueFunscriptKeys")
$lines.Add('')
$lines.Add("Movies missing funscript: $($moviesMissingSorted.Count)")
$lines.Add("Funscripts missing movie: $($funscriptsMissingSorted.Count)")
$lines.Add('============================================================')
$lines.Add('')
$lines.Add('[1] MOVIES WITHOUT A CORRESPONDING FUNSCRIPT')
$lines.Add('------------------------------------------------------------')
if ($moviesMissingSorted.Count -eq 0) {
$lines.Add('(none)')
} else {
foreach ($p in $moviesMissingSorted) { $lines.Add([string]$p) }
}
$lines.Add('')
$lines.Add('')
$lines.Add('[2] FUNSCRIPTS WITHOUT A CORRESPONDING MOVIE')
$lines.Add('------------------------------------------------------------')
if ($funscriptsMissingSorted.Count -eq 0) {
$lines.Add('(none)')
} else {
foreach ($p in $funscriptsMissingSorted) { $lines.Add([string]$p) }
}
$lines.Add('')
$lines.Add('============================================================')
$lines.Add('End of report.')
$lines | Set-Content -LiteralPath $ReportPath -Encoding UTF8
Write-Host "Report written to:"
Write-Host $ReportPath