Skip to content

Instantly share code, notes, and snippets.

@h8rt3rmin8r
Created February 13, 2026 05:31
Show Gist options
  • Select an option

  • Save h8rt3rmin8r/15fbaebc4fd01f77b9f2490eee60a82a to your computer and use it in GitHub Desktop.

Select an option

Save h8rt3rmin8r/15fbaebc4fd01f77b9f2490eee60a82a to your computer and use it in GitHub Desktop.
Create a multi-layer ICO file from multiple source images using FFmpeg #powershell #ffmpeg #favicon
<#
.SYNOPSIS
Creates a multi-layer ICO file from multiple source images using FFmpeg.
.DESCRIPTION
Combines individual image assets into a single ICO container. Supports piping
file objects or paths directly into the script. Dynamically maps inputs
to ensure all layers are included in the final mux.
.PARAMETER OutputPath
The destination path for the generated .ico file. Defaults to 'favicon.ico'.
.PARAMETER SourceFiles
An array of file paths to be used as icon layers. Supports pipeline input.
Mandatory for the build process.
.PARAMETER Help
Displays this help message and exits without processing.
Aliases: -h
.EXAMPLE
.\New-MultiLayerIcon.ps1 -SourceFiles "16.png", "32.png" -OutputPath "App.ico"
.EXAMPLE
Get-ChildItem .\Assets\*.png | .\New-MultiLayerIcon.ps1 -OutputPath "Bundle.ico"
.EXAMPLE
.\New-MultiLayerIcon.ps1 -Help
#>
[CmdletBinding(DefaultParameterSetName = 'Build')]
param(
[Parameter(
Position = 0,
ParameterSetName = 'Build'
)]
[string]$OutputPath = "favicon.ico",
[Parameter(
Position = 1,
Mandatory = $true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
ParameterSetName = 'Build'
)]
[Alias("FullName", "Path")]
[string[]]$SourceFiles,
[Parameter(ParameterSetName = 'Help')]
[Alias('h')]
[Switch]$Help
)
begin {
# Check for Help switch immediately to bypass dependency checks
if ($Help) {
# Retrieve the path to this executing script and invoke Get-Help on it
Get-Help $MyInvocation.MyCommand.Path -Detailed
return # Exit the Begin block (and effectively the script)
}
Write-Verbose "Initializing Icon Build Process..."
# Check for FFmpeg dependency
if (-not (Get-Command ffmpeg -ErrorAction SilentlyContinue)) {
Write-Error "FFmpeg was not found in the system PATH. Please install it to continue."
break
}
# Internal list to collect piped or array-based inputs
$InputList = New-Object System.Collections.Generic.List[string]
}
process {
# If we are in Help mode, skip processing
if ($Help) { return }
# Collect inputs from the pipeline or the parameter array
foreach ($File in $SourceFiles) {
$Item = Get-Item $File -ErrorAction SilentlyContinue
if ($Item) {
$InputList.Add($Item.FullName)
Write-Verbose "Added layer: $($Item.FullName)"
} else {
Write-Warning "File not found and skipped: $File"
}
}
}
end {
# If we are in Help mode, exit gracefully
if ($Help) { return }
if ($InputList.Count -eq 0) {
Write-Error "No valid source files were provided."
return
}
Write-Host "Processing $($InputList.Count) layers into $OutputPath..." -ForegroundColor Cyan
# Dynamically build FFmpeg arguments
$FFmpegArgs = New-Object System.Collections.Generic.List[string]
# 1. Add Input Flags
foreach ($Path in $InputList) {
$FFmpegArgs.Add("-i")
$FFmpegArgs.Add($Path)
}
# 2. Add Mapping Flags (Stream #0:0, #1:0, etc.)
for ($i = 0; $i -lt $InputList.Count; $i++) {
$FFmpegArgs.Add("-map")
$FFmpegArgs.Add("$($i):0")
}
# 3. Add Output and Overwrite
$FFmpegArgs.Add("-y")
$FFmpegArgs.Add($OutputPath)
try {
# Execute FFmpeg with the dynamic argument list
$ProcessParams = @{
FilePath = "ffmpeg"
ArgumentList = $FFmpegArgs
Wait = $true
NoNewWindow = $true
}
Start-Process @ProcessParams
if (Test-Path $OutputPath) {
$FileItem = Get-Item $OutputPath
$SizeKB = [Math]::Round($FileItem.Length / 1KB, 2)
Write-Host "Build Complete: $OutputPath ($SizeKB KB)" -ForegroundColor Green
}
}
catch {
Write-Error "FFmpeg execution failed: $($_.Exception.Message)"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment