Last active
June 1, 2023 11:25
-
-
Save tmslnz/db1a16786db34884481d4ef1e6d7c39d to your computer and use it in GitHub Desktop.
PowerShell wrapper for After Effects aerender.exe
This file contains 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
<# | |
After Effects aerender.exe wrapper script | |
#> | |
[CmdletBinding(PositionalBinding = $false)] | |
param( | |
[String][parameter(Position=0)]$project_pparam, | |
[String]$project, | |
[String]$comp = "Main", | |
[string]$resolution = "Full", | |
[int]$mem = 40, | |
[int]$cpu = 60, | |
[switch]$force = $false, | |
[Nullable[int]]$in = $null, | |
[NUllable[int]]$out = $null, | |
[String]$preset = "prores", | |
[switch]$sync = $false, | |
[switch]$version = $false, | |
[switch]$turbo = $false | |
) | |
if ($version -eq $true) { | |
Write-Host "17" | |
Exit | |
} | |
if ('' -eq $project -and '' -eq $project_pparam) { | |
Write-Warning "Project file paramter is missing. Exiting.`n$($project)" | |
Exit | |
} | |
if ('' -eq $project) { | |
$project = $project_pparam | |
} | |
if ($turbo -eq $true) { | |
$cpu = 90 | |
$mem = 90 | |
} | |
$project = ([System.IO.Path]::Combine((Get-Location), $project)) | |
$project_name = ([IO.FileInfo]$project).BaseName | |
$project_dir = ([IO.FileInfo]$project).Directory | |
$output_name = "$($project_name) $($comp) $($preset.ToLower())" | |
$ae_logs_dir = ([IO.Path]::Combine($project_dir, "$($project_name).aep Logs")) | |
$output_dir = ([IO.Path]::Combine(([IO.FileInfo]$project).DirectoryName, "Renders", "$($output_name).nosync")) | |
if ($sync -eq $true) { | |
$output_dir = ([IO.Path]::Combine(([IO.FileInfo]$project).DirectoryName, "Renders", "$($output_name)")) | |
} | |
$output_file = ([IO.Path]::Combine($output_dir, "[#####]")) | |
$ae = "$Env:Programfiles\Adobe\Adobe After Effects 2022\Support Files\aerender.exe" | |
$process_out_file = ([IO.Path]::Combine($env:TEMP, "aerender $($project_name) $($comp) $($preset).txt")) | |
$output_presets = @{} | |
$output_presets.Add('prores', 'High Quality') | |
$output_presets.Add('prores+alpha', 'High Quality with Alpha') | |
$output_presets.Add('tiff', 'TIFF Sequence with Alpha') | |
$output_preset = if ($output_presets[$preset.ToLower()]) { $output_presets[$preset.ToLower()] } else { $preset } | |
# Abort if .aep file does not exist | |
if (-Not [System.IO.File]::Exists($project)) { | |
Write-Warning "`nInvalid project file path. Exiting.`n$($project)" | |
Exit | |
} | |
# Abort if the output already exists, unless -force is used | |
if ([System.IO.Directory]::Exists($output_dir) -and !$force) { | |
Write-Warning "`nOutput directory already exists. Exiting.`n$($output_dir)" | |
Exit | |
} | |
# Print summary of changed values | |
if ($in -ne $null) { | |
Write-Host " In: $($in) " -BackgroundColor Green | |
} | |
if ($out -ne $null) { | |
Write-Host " Out: $($out) " -BackgroundColor Green | |
} | |
if ($sync -ne $false) { | |
Write-Host " Sync: YES " -BackgroundColor Green | |
} | |
if ($mem -ne 40) { | |
Write-Host " Max Memory: $($mem)% " -BackgroundColor Green | |
} | |
if ($comp -ne 'Main') { | |
Write-Host " Comp: $($comp) " -BackgroundColor Green | |
} | |
if ($preset -ine 'prores') { | |
Write-Host " Preset: $($output_preset) " -BackgroundColor Green | |
} | |
# Sleep 5s if -force is requested | |
if ($force -eq $true) { | |
Write-Host " Will Overwrite!! Starting in 5s " -BackgroundColor Red | |
Start-Sleep -Seconds 5 | |
} | |
# Utility to get child processes of aerender.exe | |
function Get-ChildProcesses ($ParentProcessId) { | |
$filter = "parentprocessid = '$($ParentProcessId)'" | |
Get-CIMInstance -ClassName win32_process -filter $filter | Foreach-Object { | |
$_ | |
if ($_.ParentProcessId -ne $_.ProcessId) { | |
Get-ChildProcesses $_.ProcessId | |
} | |
} | |
} | |
# Make destination directory if it doesn't exist | |
New-Item -Path $output_dir -ItemType "directory" -ErrorAction Ignore | Out-Null | |
# Build arguents list | |
$args_list = @( | |
"-mfr", "ON", "$($cpu)", | |
"-mem_usage", "$($mem)", "$($mem)", | |
"-project", "`"$($project)`"", | |
"-output", "`"$($output_file)`"", | |
"-comp", "`"$($comp)`"", | |
"-renderSettings", "`"Resolution: $($resolution)`"", | |
"-OMtemplate", "`"$($output_preset)`"" | |
) | |
if ($in -ne $null) { $args_list += "-s", "$($in)" } | |
if ($out -ne $null) { $args_list += "-e", "$($out)" } | |
# Using Start Process | |
$process = Start-Process $ae -NoNewWindow -ArgumentList $args_list -PassThru -RedirectStandardOutput $process_out_file | |
if ($turbo -eq $false) { | |
$process.PriorityClass = [System.Diagnostics.ProcessPriorityClass]::BelowNormal | |
} | |
$render_process_id = Get-ChildProcesses $process.Id | Where-Object -Property Name -eq "AfterFX.com" | Select-Object -ExpandProperty ProcessId | |
# Read output file in a loop. It is not possible to have start-process putput to file & console, but we need start-process to set the priority. | |
$prev_line = 0 | |
while (Get-Process -Id $process.Id -ErrorAction Ignore) { | |
Start-Sleep -Milliseconds 250 | |
$cur_line = Get-Content $process_out_file | Measure-Object -Line | Select-Object -ExpandProperty Lines | |
if ($cur_line -gt $prev_line) { | |
Get-Content $process_out_file | Select-Object -Skip ($prev_line + 1) | |
} | |
$prev_line = $cur_line | |
[Console]::TreatControlCAsInput = $True | |
if ($Host.UI.RawUI.KeyAvailable -and (3 -eq [int]$Host.UI.RawUI.ReadKey("AllowCtrlC,IncludeKeyUp,NoEcho").Character)) { | |
Stop-Process -Id $process.Id -Force | |
Stop-Process -Id $render_process_id -Force | |
Write-Warning "Exiting" | |
exit 1 | |
} | |
} | |
# Pause script until AE is done | |
$process.WaitForExit() | |
# Store the current log file count | |
$log_files_count = (Get-ChildItem -Path $output_dir -File -Recurse -Include *.txt | Measure-Object).Count | |
# Search the process output file after exit to determine if AE exited with error | |
$aerender_error = Select-String -Path $process_out_file -SimpleMatch -Quiet -Pattern 'aerender Error','Error Code' | |
$logfile_closed = Select-String -Path $process_out_file -SimpleMatch -Quiet -Pattern "Log closed on" | |
if ($logfile_closed -eq $false -and $aerender_error -eq $true) { | |
$process_out_filename = ([IO.FileInfo]$process_out_file).Name | |
Copy-Item $process_out_file -Destination $output_dir | |
Rename-Item -Path ([IO.Path]::Combine($output_dir, $process_out_filename)) -NewName "$($log_files_count) $($process_out_filename)" | |
Write-Warning "`nAfter Effects errored out. Log saved in Renders dir.`nExiting." | |
Exit 1 | |
} | |
# Copy last log file into output folder and prepend an index | |
$log_file = Get-ChildItem -Path $ae_logs_dir -ErrorAction Ignore | Select-Object -last 1 | |
if ($null -ne $log_file) { | |
Copy-Item $log_file.FullName -Destination $output_dir -ErrorAction Ignore | |
Rename-Item -Path ([IO.Path]::Combine($output_dir, $log_file)) -NewName "$($log_files_count) $($log_file)" -ErrorAction Ignore | |
} | |
# Rename video file for convenience -- only heppns when output is QuickTime | |
Move-Item -LiteralPath ([IO.Path]::Combine($output_dir, '[#####].mov')) -Destination ([IO.Path]::Combine($output_dir, "$($output_name).mov")) -Force -ErrorAction Ignore | |
# Delete any leftover "*.aep Logs" folders | |
Remove-Item -Path ([IO.Path]::Combine($project_dir, "$($project_name).aep Logs")) -Recurse -Force -ErrorAction Ignore | |
# Functions | |
function Show-Notification { | |
[cmdletbinding()] | |
Param ( | |
[string] | |
$ToastTitle, | |
[string] | |
[parameter(ValueFromPipeline)] | |
$ToastText | |
) | |
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > $null | |
$Template = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02) | |
$RawXml = [xml] $Template.GetXml() | |
($RawXml.toast.visual.binding.text|Where-Object {$_.id -eq "1"}).AppendChild($RawXml.CreateTextNode($ToastTitle)) > $null | |
($RawXml.toast.visual.binding.text|Where-Object {$_.id -eq "2"}).AppendChild($RawXml.CreateTextNode($ToastText)) > $null | |
$SerializedXml = New-Object Windows.Data.Xml.Dom.XmlDocument | |
$SerializedXml.LoadXml($RawXml.OuterXml) | |
$Toast = [Windows.UI.Notifications.ToastNotification]::new($SerializedXml) | |
$Toast.Tag = "After Effects" | |
$Toast.Group = "After Effects" | |
$Toast.ExpirationTime = [DateTimeOffset]::Now.AddMinutes(1) | |
$Notifier = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("After Effects") | |
$Notifier.Show($Toast); | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment