Created
February 13, 2026 05:31
-
-
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
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
| <# | |
| .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