Skip to content

Instantly share code, notes, and snippets.

@tmslnz
Last active June 1, 2023 11:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tmslnz/db1a16786db34884481d4ef1e6d7c39d to your computer and use it in GitHub Desktop.
Save tmslnz/db1a16786db34884481d4ef1e6d7c39d to your computer and use it in GitHub Desktop.
PowerShell wrapper for After Effects aerender.exe
<#
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