Skip to content

Instantly share code, notes, and snippets.

@arenagroove
Created April 22, 2025 04:18
Show Gist options
  • Select an option

  • Save arenagroove/f4f146187540ebcb5eda26f99f0c56fd to your computer and use it in GitHub Desktop.

Select an option

Save arenagroove/f4f146187540ebcb5eda26f99f0c56fd to your computer and use it in GitHub Desktop.
PowerShell script to generate multi-size, multi-format thumbnails with exact dimensions. Supports cropping, aspect ratio control, format selection (JPG, PNG, WebP), and custom filename suffixes.

Multi-Thumbnail Generator (PowerShell + FFmpeg)

This PowerShell script generates WordPress-style image thumbnails at multiple sizes and formats with cropping and aspect ratio control. It uses FFmpeg for scaling, cropping, and encoding images into JPG, PNG, or WebP formats.

Features

  • ✅ Generate thumbnails at any number of target widths
  • ✅ Supports output in jpg, png, and webp formats
  • ✅ Optional forced aspect ratio override (e.g., 16:9, 1:1)
  • ✅ Precise cropping to produce exact WIDTH x HEIGHT dimensions
  • ✅ Optional filename suffix (e.g., thumb-33image-thumb-33-300x169.jpg)
  • ✅ Automatically cleans up previous thumbnails before generation
  • ✅ Clean FFmpeg integration with format-specific quality settings

Usage

  1. Requirements:

    • PowerShell (v5 or higher)
    • FFmpeg installed and added to your system PATH
    • FFprobe (comes with FFmpeg)
  2. Place the script in the folder containing your image files.

  3. Customize the configuration at the top of the script:

    $targetWidths     = @(820, 435, 300)
    $outputFormats    = @("jpg", "png", "webp")
    $forceAspectRatio = "16:9"     # Or $null for original aspect ratio
    $nameSuffix       = "thumb-33" # Optional: add between base name and size
# -------------------------------------------------------------------
# WordPress-Style Thumbnail Generator with Cropping (PowerShell + FFmpeg)
# -------------------------------------------------------------------
# === Configuration ===
$targetWidths = @(820, 435, 300)
$outputFormats = @("jpg") # @("jpg", "png", "webp") Supported formats
$forceAspectRatio = "1:1" # e.g., "16:9", "1:1", or $null
$nameSuffix = "" # Optional suffix before WxH, e.g. "md-down"; set to "" to disable
# Format-specific quality settings
$jpgQuality = 2 # JPEG: 2 = high quality (1 best, 31 worst)
$pngCompression = 3 # PNG: 0 = no compression, 9 = max compression
$webpQuality = 95 # WebP: 0 (worst) – 100 (best)
# ======================
# === Cleanup: Delete all thumbnails like *-WIDTHxHEIGHT.anyExtension ===
$thumbPattern = "-\d+x\d+\.\w+$"
Get-ChildItem -File | Where-Object { $_.Name -match $thumbPattern } | ForEach-Object {
try {
Remove-Item -Path $_.FullName -Force -ErrorAction Stop
Write-Output "🗑️ Deleted old thumbnail: $($_.Name)"
} catch {
Write-Output "⚠️ Could not delete: $($_.Name)"
}
}
# === Thumbnail Generation ===
Get-ChildItem -File | Where-Object { $_.Extension -match "\.(jpg|png|webp)$" } | ForEach-Object {
$originalPath = $_.FullName
$filename = $_.Name
$cleanName = $_.BaseName
try {
# Get original dimensions using ffprobe
$width = ffprobe -v error -select_streams v:0 -show_entries stream=width -of csv=p=0 "`"$originalPath`""
$height = ffprobe -v error -select_streams v:0 -show_entries stream=height -of csv=p=0 "`"$originalPath`""
$width = [int]$width
$height = [int]$height
if ($width -eq 0 -or $height -eq 0) {
Write-Output "⚠️ Skipping $filename (invalid dimensions)"
return
}
} catch {
Write-Output "❌ Failed to probe $filename. Skipping."
return
}
$OAR = $width / $height # Original Aspect Ratio
foreach ($targetWidth in $targetWidths) {
# === Determine target height ===
if ($forceAspectRatio) {
$parts = $forceAspectRatio -split ":"
$faWidth = [int]$parts[0]
$faHeight = [int]$parts[1]
$targetHeight = [math]::Round($targetWidth * ($faHeight / $faWidth))
$TAR = $faWidth / $faHeight
Write-Output "📐 Using forced aspect ratio $forceAspectRatio → ${targetWidth}x${targetHeight}"
} else {
$targetHeight = [math]::Round($targetWidth / $OAR)
$TAR = $targetWidth / $targetHeight
}
# === Cropping logic for consistent WxH thumbnails ===
if ([math]::Round($OAR, 3) -eq [math]::Round($TAR, 3)) {
$vfFilter = "scale=${targetWidth}:${targetHeight},setsar=1"
}
elseif ($OAR -gt $TAR) {
$vfFilter = "scale=-2:${targetHeight},crop=${targetWidth}:${targetHeight}:(iw-${targetWidth})/2:0,setsar=1"
}
else {
$vfFilter = "scale=${targetWidth}:-2,crop=${targetWidth}:${targetHeight}:0:(ih-${targetHeight})/2,setsar=1"
}
foreach ($fmt in $outputFormats) {
# === Construct output filename with optional suffix ===
if ($nameSuffix -ne "") {
$outputName = "${cleanName}-${nameSuffix}-${targetWidth}x${targetHeight}.${fmt}"
} else {
$outputName = "${cleanName}-${targetWidth}x${targetHeight}.${fmt}"
}
switch ($fmt.ToLower()) {
"jpg" {
$cmd = "ffmpeg -i `"$originalPath`" -vf `"$vfFilter`" -q:v $jpgQuality `"$outputName`" -y"
}
"png" {
$cmd = "ffmpeg -i `"$originalPath`" -vf `"$vfFilter`" -compression_level $pngCompression `"$outputName`" -y"
}
"webp" {
$cmd = "ffmpeg -i `"$originalPath`" -vf `"$vfFilter`" -q:v $webpQuality `"$outputName`" -y"
}
default {
Write-Output "❌ Unsupported format: $fmt — skipping."
continue
}
}
try {
Invoke-Expression $cmd
Write-Output "✅ $filename → $outputName"
} catch {
Write-Output "❌ Error generating ${outputName}: $_"
}
}
}
}
Write-Output "`n✅ Thumbnail generation complete."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment