|
# ------------------------------------------------------------------- |
|
# 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." |