Last active
December 26, 2024 03:29
-
-
Save omarsaad98/9a542b6102c5e07302af96153388f08d to your computer and use it in GitHub Desktop.
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
# This PowerShell script performs a 2-pass encode on a video using ffmpeg. | |
# The video is trimmed to the given start and end times, and the output file size is targeted to 8 MiB. | |
param( | |
[string]$InputFile, | |
[string]$StartTime, | |
[string]$EndTime, | |
[string]$OutputFile | |
) | |
# Constants | |
$TargetSizeBytes = 8 * 1024 * 1024 # 8 MiB | |
$AudioBitrate = 96000 # 96 kbps | |
$SizeMargin = 0.2 * 1024 * 1024 # 0.2 MiB | |
# Ensure ffmpeg is available | |
if (-not (Get-Command "ffmpeg" -ErrorAction SilentlyContinue)) { | |
Write-Error "ffmpeg is not installed or not in the PATH."; exit 1 | |
} | |
# Ensure ffprobe is available | |
if (-not (Get-Command "ffprobe" -ErrorAction SilentlyContinue)) { | |
Write-Error "ffprobe is not installed or not in the PATH."; exit 1 | |
} | |
# Run ffprobe to get video and audio durations (used for bitrate calculation) | |
Write-Host "Calculating video duration..." | |
ffprobe -i $InputFile -show_entries format=duration -v quiet -of csv="p=0" 2>&1 | Tee-Object -Variable VideoInfo | |
if ($VideoInfo -match "^(\d+\.?\d*)$") { | |
$Duration = [decimal]$matches[1] | |
} else { | |
Write-Error "Unable to determine video duration."; exit 1 | |
} | |
# Get source video FPS | |
Write-Host "Detecting source video FPS..." | |
ffprobe -v error -select_streams v:0 -show_entries stream=r_frame_rate -of default=noprint_wrappers=1:nokey=1 $InputFile 2>&1 | Tee-Object -Variable FPSInfo | |
if ($FPSInfo -match "(\d+)/(\d+)") { | |
$SourceFPS = [math]::Round([decimal]$matches[1] / [decimal]$matches[2], 3) | |
Write-Host "Source video FPS: $SourceFPS" | |
} else { | |
Write-Error "Unable to determine source video FPS."; exit 1 | |
} | |
# Construct FPS filter based on source FPS | |
$FPSFilter = if ($SourceFPS -gt 60) { ",fps=60" } else { "" } | |
$StartSeconds = [timespan]::Parse($StartTime).TotalSeconds | |
$EndSeconds = [timespan]::Parse($EndTime).TotalSeconds | |
if ($EndSeconds -lt 0 -or $EndSeconds -gt $Duration -or $EndSeconds -le $StartSeconds) { | |
Write-Error "Invalid end time. Ensure StartTime and EndTime are correct and within the video duration."; exit 1 | |
} | |
$TrimDuration = $EndSeconds - $StartSeconds | |
# Calculate video bitrate for first pass | |
$AudioSize = $TrimDuration * ($AudioBitrate / 8) | |
$VideoSize = $TargetSizeBytes - $AudioSize - $SizeMargin | |
if ($VideoSize -lt 0) { | |
Write-Error "Target size is too small for the given audio bitrate and duration."; exit 1 | |
} | |
$VideoBitrate = [math]::Floor($VideoSize * 8 / $TrimDuration) | |
Write-Host "Target video bitrate: $VideoBitrate" | |
Write-Host "Target size in MiB: $([math]::Round(($VideoSize + $AudioSize) / 1024 / 1024, 2))" | |
# Determine maximum resolution | |
if ($TrimDuration -gt 10) { | |
$MaxWidth = 1280 | |
$MaxHeight = 720 | |
} else { | |
$MaxWidth = 1920 | |
$MaxHeight = 1080 | |
} | |
# First Pass | |
Write-Host "Running first pass..." | |
ffmpeg -hide_banner -y -i $InputFile -ss $StartTime -to $EndTime -vf "scale=w=min(iw\,$MaxWidth):h=min(ih\,$MaxHeight):force_original_aspect_ratio=decrease$FPSFilter" -an -c:v libx264 -b:v ${VideoBitrate} -preset veryslow -f null NUL 2>&1 | Tee-Object -Variable FirstPassOutput | |
if ($LastExitCode -ne 0) { Write-Error "First pass failed."; exit 1 } | |
# Parse final ratefactor from ffmpeg output | |
$AverageCRF = $FirstPassOutput | Select-String -Pattern "final ratefactor: (\d+\.?\d*)" | ForEach-Object { [double]$_.Matches.Groups[1].Value } | |
if (-not $AverageCRF) { Write-Error "Unable to parse average CRF from pass log."; exit 1 } | |
Write-Host "Average CRF: $AverageCRF" | |
$AudioTracks = (ffprobe -v error -select_streams a -show_entries stream=index -of csv=p=0 "$InputFile" | Measure-Object -Line).Lines | |
if ($AudioTracks -lt 1) { Write-Error "No audio tracks found in input file"; exit 1 } | |
# Second Pass | |
Write-Host "Running second pass..." | |
ffmpeg -hide_banner -y -i $InputFile -ss $StartTime -to $EndTime -filter_complex "[0:a]amix=inputs=$AudioTracks[aout]" -vf "scale=w=min(iw\,$MaxWidth):h=min(ih\,$MaxHeight):force_original_aspect_ratio=decrease$FPSFilter" -map 0:v:0 -map "[aout]" -c:v libx264 -preset veryslow -crf $AverageCRF -c:a aac -b:a ${AudioBitrate} $OutputFile | |
if ($LastExitCode -ne 0) { Write-Error "Second pass failed."; exit 1 } | |
Write-Host "Encoding completed successfully. Output saved to $OutputFile." | |
# Check output file size compared to the target size | |
$OutputFileSize = (Get-Item $OutputFile).Length | |
$OutputFileSizeMB = [math]::Round($OutputFileSize / 1024 / 1024, 2) | |
Write-Host "Output file size: $OutputFileSizeMB MiB" | |
# Show the resulting file in file explorer | |
explorer.exe "/select,$OutputFile" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment