Created
February 2, 2026 11:07
-
-
Save pleabargain/6882cb0ba615d87ceaeb3d21a4bbba67 to your computer and use it in GitHub Desktop.
ps1 compress videos to under 20MB
This file contains hidden or 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
| <# | |
| .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