Skip to content

Instantly share code, notes, and snippets.

@Venipa
Created August 11, 2025 14:44
Show Gist options
  • Save Venipa/f04c51e318c003cda0a0b1632a31b1e3 to your computer and use it in GitHub Desktop.
Save Venipa/f04c51e318c003cda0a0b1632a31b1e3 to your computer and use it in GitHub Desktop.
Moves video files under folders to the upper level and adopts the folder name.
# Moves video files under folders to the upper level and adopts the folder name.
# for example FolderA\FileA.mp4 => FolderA.mp4
# duplicates will append a number, FolderA_<1-100>.mp4, max 100 files can be moved to the upper level.
#
# Use cases:
# - Movie folders with the movie inside
# - Single Album Covers with single or multiple of the same cover.
#
# Check if the first argument is a folder; otherwise, prompt for a folder
# Check if the first argument exists and is a folder; if so, use it as the base directory
# Use WPF Open Directory Dialog instead of WinForms
Add-Type -AssemblyName PresentationFramework
function Select-FolderDialogWPF {
$dialog = New-Object Microsoft.Win32.OpenFileDialog
$dialog.CheckFileExists = $false
$dialog.FileName = "Select Folder"
$dialog.ValidateNames = $false
$dialog.Filter = "Folders|."
if ($dialog.ShowDialog() -eq $true) {
return ([System.IO.Path]::GetDirectoryName($dialog.FileName))
} else {
return $null
}
}
if ($args.Count -gt 0 -and (Test-Path $args[0] -PathType Container)) {
$baseDir = $args[0]
} else {
$baseDir = Select-FolderDialogWPF
if (-not $baseDir) {
Write-Host "No folder selected. Exiting." -ForegroundColor Red
exit
}
}
function Exit-WithAnyKey {
Write-Host "Press any key to exit..."
[void][System.Console]::ReadKey($true)
exit
}
if ($baseDir -like "C:\Windows*" -or (Resolve-Path $baseDir).ProviderPath -like "C:\Windows*") {
Write-Host "Error: The selected base directory is inside C:\Windows. Exiting for safety." -ForegroundColor Red
Exit-WithAnyKey
}
# Check for dry-run parameter
$dryRun = $false
if ($args -contains "-dry" -or $args -contains "--dry-run" -or $args -contains "-d") {
$dryRun = $true
Write-Host "DRY RUN MODE - No files will be moved" -ForegroundColor Yellow
Write-Host ""
}
# Get all subfolders in the base directory
$folders = Get-ChildItem -Path $baseDir -Directory
Write-Host "Processing folders in: $baseDir" -ForegroundColor Cyan
Write-Host ""
foreach ($folder in $folders) {
Write-Host "Processing folder: $($folder.Name)" -ForegroundColor Green
# Get all .mp4 and .mkv files in the folder
$mediaFiles = Get-ChildItem -Path $folder.FullName -File | Where-Object { $_.Name -match '\.(mp4|mkv)$' }
foreach ($mediaFile in $mediaFiles) {
$ext = $mediaFile.Extension
$newBaseName = $folder.Name
$newName = "$newBaseName$ext"
# Check for duplicates and append _<number> if needed, try up to 100 times
$baseNameOnly = [System.IO.Path]::GetFileNameWithoutExtension($folder.Name)
$tryName = "$baseNameOnly$ext"
$counter = 1
while ((Test-Path (Join-Path -Path $baseDir -ChildPath $tryName)) -and ($counter -le 100)) {
$tryName = "${baseNameOnly}_$counter$ext"
$counter++
}
$newName = $tryName
$destPath = Join-Path -Path $baseDir -ChildPath $newName
if ($dryRun) {
Write-Host " Would move: $($mediaFile.Name) -> $newName" -ForegroundColor White
Write-Host " From: $($mediaFile.FullName)" -ForegroundColor Gray
Write-Host " To: $destPath" -ForegroundColor Gray
} else {
Write-Host " Moving: $($mediaFile.Name) -> $newName" -ForegroundColor White
Move-Item -Path $mediaFile.FullName -Destination $destPath -Force
}
Write-Host ""
}
}
if ($dryRun) {
Write-Host "DRY RUN COMPLETE - No files were moved" -ForegroundColor Yellow
Write-Host "Run the script without -dry, --dry-run, or -d to actually move the files" -ForegroundColor Cyan
Exit-WithAnyKey
} else {
Write-Host "File conversion complete!" -ForegroundColor Green
Exit-WithAnyKey
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment