Last active
April 16, 2025 15:14
-
-
Save angstyfrostyneko/6ce11f37ae6f068247ad294cecde72e5 to your computer and use it in GitHub Desktop.
FFMPEG wrapper that compresses videos under target size
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
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." |
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 :(
- 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
- made the target bitrate change based on an rule of three approximation with the resulting size of the first encode result
- 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