Skip to content

Instantly share code, notes, and snippets.

@angstyfrostyneko
Last active April 16, 2025 15:14
Show Gist options
  • Save angstyfrostyneko/6ce11f37ae6f068247ad294cecde72e5 to your computer and use it in GitHub Desktop.
Save angstyfrostyneko/6ce11f37ae6f068247ad294cecde72e5 to your computer and use it in GitHub Desktop.
FFMPEG wrapper that compresses videos under target size
param (
[Parameter(Mandatory)][string]$video,
[float]$size = 10.0,
[string]$name = "$($size)MB $([System.IO.Path]::GetFileNameWithoutExtension($video))",
[ValidateSet("av1", "h264", "h265", "vp9")]
[string]$encoder = "h264",
[ValidateSet("amd", "nvidia", "intel", "none")]
[string]$gpu = "amd",
[ValidatePattern("\d+:\d+:\d+")]
[string]$start = "00:00:00",
[ValidatePattern("\d+:\d+:\d+")]
[string]$end = "00:00:00",
[ValidatePattern("\d+:\d+")]
[string]$aspect_ratio = "$(ffprobe -v quiet -show_entries stream=display_aspect_ratio -of default=noprint_wrappers=1:nokey=1 $video)",
[switch]$delete_original,
[switch]$preserve_logs,
[float]$volume = 1.0,
[Alias("resolution")]
[int]$height = 900,
[int]$audio_bitrate = 128
)
if ($aspect_ratio -eq "N/A") {
$w = "$(ffprobe -v error -select_streams v:0 -show_entries stream=width -of default=noprint_wrappers=1:nokey=1 $video)"
$h = "$(ffprobe -v error -select_streams v:0 -show_entries stream=height -of default=noprint_wrappers=1:nokey=1 $video)"
if ($w -gt $h) {
$a = $w
$b = $h
}
else {
$a = $h
$b = $w
}
while ($b -ne 0) {
$r = $a % $b
$a = $b
$b = $r
}
$aspect_ratio = "$([int]($w/$a)):$([int]($h/$a))"
}
if ((Get-Item $video).length / 1MB -lt $size) {
Rename-Item $video -NewName "$($name)$([System.IO.Path]::GetExtension($video))"
exit
}
$width = [math]::Round(($height / $aspect_ratio.Split(":")[1]) * $aspect_ratio.Split(":")[0])
if ($width % 2 -eq 1) { $width -= 1 }
$start_seconds = [TimeSpan]::Parse($start).TotalSeconds
$end_seconds = [TimeSpan]::Parse($end).TotalSeconds
$duration = ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 $video
if ($end_seconds -eq "0") { $end_seconds = $duration }
else { $duration -= $duration - $end_seconds }
$duration -= $start_seconds
$video_bitrate = ($size * 8KB) / $duration - $audio_bitrate
if ($video_bitrate -lt 0) {
Write-Host "With the current audio bitrate the video bitrate is negative. Please lower the audio bitrate with '-audio_bitrate'!"
exit
}
$video_codec = ""
$audio_codec = "aac"
$video_extension = "mp4"
$repeat_count = 0.01
switch ($encoder) {
"av1" {
switch ($gpu) {
"nvidia" { $video_codec = "av1_nvenc" }
"intel" { $video_codec = "av1_qsv" }
"amd" { $video_codec = "av1_amf" }
default { $video_codec = "libaom-av1" }
}
$video_extension = "mkv"
}
"vp9" {
switch ($gpu) {
"intel" { $video_codec = "vp9_qsv" }
default { $video_codec = "libvpx-vp9" }
}
$audio_codec = "libopus"
$video_extension = "webm"
}
"h264" {
switch ($gpu) {
"amd" { $video_codec = "h264_amf" }
"nvidia" { $video_codec = "h264_nvenc" }
"intel" { $video_codec = "h264_qsv" }
default { $video_codec = "libx264" }
}
}
"h265" {
switch ($gpu) {
"amd" { $video_codec = "hevc_amf" }
"nvidia" { $video_codec = "hevc_nvenc" }
"intel" { $video_codec = " hevc_qsv" }
default { $video_codec = "libx265" }
}
}
}
do {
ffmpeg `
-y `
-i $video `
-c:v $video_codec `
-ss $start_seconds `
-to $end_seconds `
-vf "scale=-2:$($height), crop=$($width):$($height)" `
-b:v "$($video_bitrate)k" `
-pass 1 `
-fps_mode vfr `
-f null NUL
ffmpeg `
-y `
-i $video `
-c:v $video_codec `
-ss $start_seconds `
-to $end_seconds `
-vf "scale=-2:$($height), crop=$($width):$($height)" `
-b:v "$($video_bitrate)k" `
-pass 2 `
-c:a $audio_codec `
-b:a "$($audio_bitrate)k" `
-filter:a "volume=$volume" `
-max_muxing_queue_size 9999 `
"$($name).$($video_extension)"
$actual_size = (Get-Item "$($name).$($video_extension)").length / 1MB
$video_bitrate = ($video_bitrate * $size) / $actual_size
$video_bitrate = $video_bitrate * (1 - $repeat_count) # kept this just in case
$repeat_count += 0.03
} while ($actual_size -gt $size)
if ($delete_original) {
Remove-Item $video
}
if (!$preserve_logs) {
Remove-Item "ffmpeg2pass-0.log" -ErrorAction SilentlyContinue
Remove-Item "ffmpeg2pass-0.log.mbtree" -ErrorAction SilentlyContinue
}
Write-Output "Video took $(($repeat_count - 0.01) / 0.03) re-encodings until it went under $size MB."
@angstyfrostyneko
Copy link
Author

angstyfrostyneko commented Sep 18, 2024

  • removed the validade range because it breaks it for some reason and I honestly can't be bothered to figure out why but otherwise the script still works
  • changed the default size from 25mb to 10 since... discord lowered the upload limit to 10 :(

@angstyfrostyneko
Copy link
Author

angstyfrostyneko commented Oct 8, 2024

  • added support for amd's av1 video encoder
  • the bitrate decreases in bigger steps the more it has to re-encode the video because it would be over the size limit

@angstyfrostyneko
Copy link
Author

  • made the target bitrate change based on an rule of three approximation with the resulting size of the first encode result

@angstyfrostyneko
Copy link
Author

  • for some reason ffprobe can give back N/A as the aspect ratio. weird. added a shitty workaround for that just in case by computing the aspect ratio manually

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment