Skip to content

Instantly share code, notes, and snippets.

@pleabargain
Created February 2, 2026 11:07
Show Gist options
  • Select an option

  • Save pleabargain/6882cb0ba615d87ceaeb3d21a4bbba67 to your computer and use it in GitHub Desktop.

Select an option

Save pleabargain/6882cb0ba615d87ceaeb3d21a4bbba67 to your computer and use it in GitHub Desktop.
ps1 compress videos to under 20MB
<#
.SYNOPSIS
Finds video files, replaces spaces in filenames with hyphens, and compresses them.
.DESCRIPTION
This script iterates through common video file types in the current directory (and all subdirectories),
renames them by converting whitespace to hyphens, and then uses FFmpeg to
re-encode the video so the output size is between 17 MB and 19 MB.
The script uses ffprobe to get the video duration and calculates the required
total bitrate so the final file lands in the 17-19 MB range (target ~18 MB).
.PARAMETER VideoExtensions
An array of file extensions to target as video files.
.NOTES
FFmpeg and ffprobe MUST be installed and in the system's PATH for this script to work.
#>
param(
[string[]]$VideoExtensions = @("*.mp4", "*.mov", "*.mkv", "*.avi", "*.webm")
)
# --- SCRIPT INTRODUCTION ---
Write-Host "======================================================================" -ForegroundColor Yellow
Write-Host " FFmpeg Video Compression Script (Target: 17–19 MB)" -ForegroundColor Green
Write-Host "======================================================================" -ForegroundColor Yellow
Write-Host "This script will perform the following actions in the current directory and **subdirectories**:"
Write-Host "1. List all detected video files and wait for your selection."
Write-Host "2. Rename the selected file by replacing spaces with hyphens (-)."
Write-Host "3. Compress the selected video using H.264/AAC so the output is between 17 MB and 19 MB."
Write-Host "4. Create a new compressed file with 'compressed' prepended to the filename."
Write-Host ""
Write-Host "Ensure FFmpeg is installed and accessible via your system's PATH." -ForegroundColor White
Write-Host "Press Ctrl+C now to cancel, or the script will proceed shortly..." -ForegroundColor Yellow
Start-Sleep -Seconds 3 # Wait 3 seconds to allow user to read and cancel
Write-Host "Starting process..." -ForegroundColor Cyan
Write-Host "----------------------------------------------------------------------" -ForegroundColor Yellow
# --- END SCRIPT INTRODUCTION ---
# --- 1. CONFIGURATION ---
# Check if FFmpeg and ffprobe are available
try {
Write-Host "Checking for FFmpeg and ffprobe..." -ForegroundColor Cyan
$null = & ffmpeg -version
$null = & ffprobe -version
} catch {
Write-Error "FFmpeg or ffprobe is not found. Please install FFmpeg and ensure it's in your system's PATH."
exit 1
}
# Target output size: 17–19 MB (use 18 MB for bitrate calculation)
$TargetSizeMB = 18
$AudioBitrateKbps = 128 # Fixed audio bitrate; video gets the rest
function Get-VideoDurationSeconds {
param([string]$FilePath)
$result = & ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 -i $FilePath 2>&1 | Out-String
$duration = ($result -replace "[\r\n].*$", "").Trim() -as [double]
if (-not $duration -or $duration -le 0) { return $null }
return $duration
}
# --- 2. FILE DISCOVERY AND SELECTION ---
Write-Host "Searching for video files in $($pwd.Path) and its subdirectories..." -ForegroundColor Yellow
$FilesToProcess = Get-ChildItem -Path (Get-Location) -Include $VideoExtensions -File -Recurse
if (-not $FilesToProcess) {
Write-Host "No video files found with extensions: $($VideoExtensions -join ', ')." -ForegroundColor Red
exit
}
$CompressedCount = 0
$ErrorCount = 0
$SelectedFile = $null
Write-Host "----------------------------------------------------------------------" -ForegroundColor Yellow
Write-Host "Discovered $($FilesToProcess.Count) video files:" -ForegroundColor Cyan
# Display files with ID, Name, and Path
Write-Host "ID Name" -ForegroundColor Green
Write-Host "-- ----" -ForegroundColor Green
$i = 1
foreach ($file in $FilesToProcess) {
Write-Host "$($i.ToString().PadLeft(2)) $($file.Name)" -ForegroundColor White
$i++
}
Write-Host ""
Write-Host "DirectoryName" -ForegroundColor Green
Write-Host "-------------" -ForegroundColor Green
$i = 1
foreach ($file in $FilesToProcess) {
Write-Host "$($i.ToString().PadLeft(2)) $($file.DirectoryName)" -ForegroundColor White
$i++
}
# Prompt for user input
do {
$Selection = Read-Host "`nEnter the ID number of the file to compress, or 'Q' to quit"
if ($Selection -ceq 'Q') {
Write-Host "Process cancelled by user." -ForegroundColor Red
exit
}
if ($Selection -match '^\d+$') {
$ID = [int]$Selection
if ($ID -ge 1 -and $ID -le $FilesToProcess.Count) {
$SelectedFileEntry = $FilesToProcess[$ID - 1] # Array is 0-based, so subtract 1
# Confirmation step
$Confirm = Read-Host "You selected file ID ${ID}: '$($SelectedFileEntry.Name)'. Continue with compression? (Y/N)"
if ($Confirm -ceq 'Y' -or $Confirm -ceq 'y') {
$SelectedFile = $SelectedFileEntry
} else {
Write-Host "Selection cancelled. Please choose another ID or 'Q' to quit." -ForegroundColor Yellow
}
} else {
Write-Host "Invalid ID. Please enter a number between 1 and $($FilesToProcess.Count)." -ForegroundColor Red
}
} else {
Write-Host "Invalid input. Please enter a number or 'Q'." -ForegroundColor Red
}
} while (-not $SelectedFile)
Write-Host "----------------------------------------------------------------------" -ForegroundColor Yellow
Write-Host "Processing selected file: $($SelectedFile.Name)" -ForegroundColor Green
Write-Host "----------------------------------------------------------------------" -ForegroundColor Yellow
# --- 2c. PROCESS THE SINGLE SELECTED FILE ---
$File = $SelectedFile # Use $File for processing consistency
# --- 2c. Rename: Replace whitespace with hyphens ---
$NewName = $File.Name -replace '\s', '-'
$NewPath = Join-Path -Path $File.DirectoryName -ChildPath $NewName
if ($File.Name -ne $NewName) {
try {
Rename-Item -Path $File.FullName -NewName $NewName -Force
Write-Host "Renamed: $($File.Name) -> $($NewName)" -ForegroundColor Green
# Update the file object to reflect the new name/path for compression
$File = Get-Item $NewPath
} catch {
Write-Error "Error renaming $($File.Name): $($_.Exception.Message)"
$ErrorCount++
# If renaming fails, stop processing this file
}
} else {
Write-Host "No whitespace found. Keeping name: $($File.Name)" -ForegroundColor DarkGreen
}
# Only attempt compression if there were no renaming errors
if ($ErrorCount -eq 0) {
$InputFile = $File.FullName
$CompressedFileName = "compressed$($File.Name)"
$CompressedFile = Join-Path -Path $File.DirectoryName -ChildPath $CompressedFileName
$OriginalSizeMB = [math]::Round($File.Length / 1MB, 2)
# Get video duration and calculate bitrate for 17–19 MB output
Write-Host "Probing video duration..." -ForegroundColor Cyan
$DurationSeconds = Get-VideoDurationSeconds -FilePath $InputFile
if (-not $DurationSeconds -or $DurationSeconds -le 0) {
Write-Error "Could not get video duration from $($File.Name). Ensure ffprobe can read the file."
$ErrorCount++
} else {
# Target 18 MB: total bitrate (kbps) = (18 * 8 * 1024) / duration = 147456 / duration
# Use 17.5 MB target to stay safely under 19 MB (container overhead)
$TargetBitsTotal = $TargetSizeMB * 8 * 1024 * 1024
$TotalBitrateKbps = [math]::Floor($TargetBitsTotal / ($DurationSeconds * 1000))
$VideoBitrateKbps = [math]::Max(200, $TotalBitrateKbps - $AudioBitrateKbps)
$FFmpegVideoBitrate = "${VideoBitrateKbps}k"
$FFmpegAudioBitrate = "${AudioBitrateKbps}k"
Write-Host "Duration: $([math]::Round($DurationSeconds, 1)) s → target ~$TargetSizeMB MB, total bitrate ~$TotalBitrateKbps kbps (video: $FFmpegVideoBitrate, audio: $FFmpegAudioBitrate)" -ForegroundColor Cyan
# Construct the FFmpeg command with calculated bitrates
$FFmpegArgs = @(
"-i", $InputFile,
"-c:v", "libx264",
"-b:v", $FFmpegVideoBitrate,
"-maxrate", $FFmpegVideoBitrate,
"-bufsize", "$([math]::Min($VideoBitrateKbps * 2, 4000))k",
"-c:a", "aac",
"-b:a", $FFmpegAudioBitrate,
"-y",
$CompressedFile
)
Write-Host "Compressing to target 17–19 MB..." -ForegroundColor Yellow
Write-Host "FFmpeg command: ffmpeg $($FFmpegArgs -join ' ')" -ForegroundColor DarkGray
try {
$FFmpegOutput = & ffmpeg @FFmpegArgs 2>&1
$FFmpegExitCode = $LASTEXITCODE
if (Test-Path $CompressedFile) {
$NewSizeMB = [math]::Round((Get-Item $CompressedFile).Length / 1MB, 2)
Write-Host "Compression complete. New file size: $($NewSizeMB) MB (Original: $($OriginalSizeMB) MB)." -ForegroundColor Green
Write-Host "Created compressed file: $CompressedFileName" -ForegroundColor Green
if ($NewSizeMB -ge 17 -and $NewSizeMB -le 19) {
Write-Host "Output is within target range (17–19 MB)." -ForegroundColor Green
} elseif ($NewSizeMB -gt 19) {
Write-Host "WARNING: Output ($($NewSizeMB) MB) is above 19 MB. Try running again or use a longer source; bitrate was calculated for ~$TargetSizeMB MB." -ForegroundColor Yellow
} else {
Write-Host "Output ($($NewSizeMB) MB) is below 17 MB. Quality could be higher; consider a larger target if desired." -ForegroundColor Yellow
}
$CompressedCount++
} else {
Write-Host "FFmpeg failed to create the compressed file: $($CompressedFile)" -ForegroundColor Red
Write-Host "FFmpeg exit code: $FFmpegExitCode" -ForegroundColor Red
Write-Host "FFmpeg output:" -ForegroundColor Red
Write-Host $FFmpegOutput -ForegroundColor Red
$ErrorCount++
}
} catch {
Write-Error "An unexpected error occurred during FFmpeg execution on $($File.Name): $($_.Exception.Message)"
$ErrorCount++
}
}
}
# --- 3. FINAL SUMMARY ---
Write-Host ""
Write-Host "=== Script Summary ===" -ForegroundColor Yellow
Write-Host "Processed 1 file (ID $ID)."
Write-Host "Successfully compressed: $CompressedCount" -ForegroundColor Green
Write-Host "Errors encountered: $ErrorCount" -ForegroundColor Red
if ($ErrorCount -eq 0 -and $CompressedCount -eq 1) {
Write-Host "The selected file was processed successfully." -ForegroundColor Green
} elseif ($CompressedCount -eq 0 -and $ErrorCount -gt 0) {
Write-Host "The selected file failed to process due to an error." -ForegroundColor Red
} elseif ($ErrorCount -eq 0 -and $CompressedCount -eq 0) {
Write-Host "No files were compressed." -ForegroundColor Red
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment