Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save thomas694/40895b7e8237d6396292ff9cf04b4fa4 to your computer and use it in GitHub Desktop.

Select an option

Save thomas694/40895b7e8237d6396292ff9cf04b4fa4 to your computer and use it in GitHub Desktop.
Convert GIF files in folder tree to WebP or WebM format

Convert GIF files in folder tree to WebP and WebM format

The script converts all GIF files in the specified folder tree to WebP and WebM format running four workers in parallel

The attached powershell script searches all GIF files in the specified folder (including its subfolders) and converts small files to WebP and the other files to WebM, using a treshold of 500KB. If there is already a conversion from a previous run, the file is skipped. The filenames of the converted files get the extension for the target format appended.

For a conversion to WebP format the following command is used:

$fn_webp = $item.FullName + ".webp"
C:\Tools\libwebp-1.2.2-windows-x64\bin\gif2webp -mt -mixed "$($item.FullName)" -o $fn_webp

For a conversion to WebM format a two-pass conversion is executed:

$fn_webm = $item.FullName + ".webm"
C:\Tools\ffmpeg-n5.0-latest-win64-gpl-shared-5.0\bin\ffmpeg -i "$($item.FullName)" -c:v libvpx-vp9 -threads 1 -lag-in-frames 0 -b:v 0 -crf 25 -pass 1 -passlogfile $prefix -an -f null NUL
C:\Tools\ffmpeg-n5.0-latest-win64-gpl-shared-5.0\bin\ffmpeg -i "$($item.FullName)" -c:v libvpx-vp9 -threads 1 -lag-in-frames 0 -b:v 0 -crf 25 -pass 2 -passlogfile $prefix -an $fn_webm

You need to adjust the paths in the script to your installation folders of ffmpeg and gif2webp.

The script makes use of an example implementation of producer / consumer parallelism in PowerShell by Lee Holmes.

<#PSScriptInfo
.VERSION 1.0
.GUID bfb939b9-03f0-433e-ad0f-e4e12f4a009c
.AUTHOR Lee Holmes
#>
<#
.DESCRIPTION
Example implementation of producer / consumer parallelism in PowerShell
#>
[Console]::TreatControlCAsInput = $true
if (!$args[0]) {
Write-Host "No Folder specified!"
Exit
}
## The script block we want to run in parallel. Threads will all
## retrieve work from $InputQueue, and send results to $OutputQueue
$parallelScript = {
param(
## An Input queue of work to do
$InputQueue,
## The output buffer to write responses to
$OutputQueue,
## State tracking, to help threads communicate
## how much progress they've made
$OutputProgress, $ThreadId, $ShouldExit,
## Script location
$ScriptPath
)
## Continually try to fetch work from the input queue, until
## the 'ShouldExit' flag is set
$processed = 0
$workItem = $null
while(! $ShouldExit.Value)
{
if ([Console]::KeyAvailable){
$readkey = [Console]::ReadKey($true)
if ($readkey.Modifiers -eq "Control" -and $readkey.Key -eq "C"){
$OutputQueue.Enqueue("Ctrl-C pressed...")
return
}
}
if($InputQueue.TryDequeue([ref] $workItem))
{
## If we got a work item, do something with it.
$worker_name = "worker${PID}_$ThreadId"
$script = "$ScriptPath\convert_gif_to_webp_webm_worker.ps1"
#Try
#{
#Invoke-Expression "$script $worker_name `"$workItem`""
& $script $worker_name $workItem
#}
#Catch
#{
# $OutputQueue.Enqueue($_.Exception.Message)
#}
$workItemResult = "$LastExitCode $workItem"
## Add the result to the output queue
$OutputQueue.Enqueue($workItemResult)
## Update our progress
$processed++
$OutputProgress[$ThreadId] = $processed
}
else
{
## If there was no work, wait a bit for more.
Start-Sleep -m 100
}
}
}
## Create a set of background PowerShell instances to do work, based on the
## number of available processors.
#$threads = Get-WmiObject Win32_Processor | Foreach-Object NumberOfLogicalProcessors
$threads = 4
$runspaces = 1..$threads | Foreach-Object { [PowerShell]::Create() }
$outputProgress = New-Object 'Int[]' $threads
$inputQueue = New-Object 'System.Collections.Concurrent.ConcurrentQueue[String]'
$outputQueue = New-Object 'System.Collections.Concurrent.ConcurrentQueue[String]'
$shouldExit = $false
$scriptPath = $PSScriptRoot
## Spin up each of our PowerShell runspaces. Once invoked, these are actively
## waiting for work and consuming once available.
for($counter = 0; $counter -lt $threads; $counter++)
{
$null = $runspaces[$counter].AddScript($parallelScript).
AddParameter("InputQueue", $inputQueue).
AddParameter("OutputQueue", $outputQueue).
AddParameter("OutputProgress", $outputProgress).
AddParameter("ThreadId", $counter).
AddParameter("ShouldExit", [ref] $shouldExit).
AddParameter("ScriptPath", $scriptPath).BeginInvoke()
}
## Qeueu some work
$path = $args[0]
$estimated = 0
foreach ($item in Get-ChildItem $path -Recurse -Filter *.gif)
{
if (!$item.FullName.Contains("\filepaths_to_exclude\") -and
!$item.FullName.Contains("skip"))
{
$currentInput = $item.FullName
$inputQueue.Enqueue($currentInput)
$estimated++
}
}
## Wait for our worker threads to complete processing the
## work.
try
{
do
{
## Update the status of how many items we've processed, based on adding up the
## output progress from each of the worker threads
$totalProcessed = $outputProgress | Measure-Object -Sum | Foreach-Object Sum
if ($estimated -gt 0)
{
Write-Progress "Processed $totalProcessed of $estimated" -PercentComplete ($totalProcessed * 100 / $estimated)
}
## If there were any results, output them.
$scriptOutput = $null
while($outputQueue.TryDequeue([ref] $scriptOutput))
{
$scriptOutput
}
## If the threads are done processing the input we gave them, let them know they can exit
if($inputQueue.Count -eq 0)
{
$shouldExit = $true
}
Start-Sleep -m 100
## See if we still have any busy runspaces. If not, exit the loop.
$busyRunspaces = $runspaces | Where-Object { $_.InvocationStateInfo.State -ne 'Complete' }
} while($busyRunspaces)
}
finally
{
## Clean up our PowerShell instances
foreach($runspace in $runspaces)
{
$runspace.Stop()
$runspace.Dispose()
}
}
[Console]::TreatControlCAsInput = $true
if (!$args[0]) {
Write-Host "No window prefix specified!"
Exit -1
}
if (!$args[1]) {
Write-Host "No File specified!"
Exit -2
}
$prefix = $args[0]
$path = $args[1]
#foreach ($item in Get-ChildItem $path -Recurse -Filter *.gif)
$item = Get-ChildItem -LiteralPath $path
#{
$fn_webm = $item.FullName + ".webm"
$fn_webp = $item.FullName + ".webp"
if (!$item.FullName.Contains("skip") -and !(Test-Path -LiteralPath $fn_webm -PathType Leaf) -and !(Test-Path -LiteralPath $fn_webp -PathType Leaf)) {
$size = $item.length
if ($size -ge 500KB) {
Set-Location -Path "$PSScriptRoot"
C:\Tools\ffmpeg-n5.0-latest-win64-gpl-shared-5.0\bin\ffmpeg -i "$($item.FullName)" -c:v libvpx-vp9 -threads 1 -lag-in-frames 0 -b:v 0 -crf 25 -pass 1 -passlogfile $prefix -an -f null NUL
if( $LastExitCode -ne 0 ) {
# conversion failed
Exit -3
}
C:\Tools\ffmpeg-n5.0-latest-win64-gpl-shared-5.0\bin\ffmpeg -i "$($item.FullName)" -c:v libvpx-vp9 -threads 1 -lag-in-frames 0 -b:v 0 -crf 25 -pass 2 -passlogfile $prefix -an $fn_webm
if( $LastExitCode -ne 0 ) {
# conversion failed
if (Test-Path -LiteralPath $fn_webm -PathType Leaf) {
Remove-Item $fn_webm
}
Exit -4
}
# set time to original file's one
$oldDate = $item.LastWriteTimeUtc
$webm = Get-ChildItem -LiteralPath $fn_webm
if ($item.LastWriteTimeUtc -ne $webm.LastWriteTimeUtc) {
$webm.LastWriteTimeUtc = $oldDate
}
} else {
C:\Tools\libwebp-1.2.2-windows-x64\bin\gif2webp -mt -mixed "$($item.FullName)" -o $fn_webp
# set time to original file's one
$oldDate = $item.LastWriteTimeUtc
$webp = Get-ChildItem -LiteralPath $fn_webp
if ($item.LastWriteTimeUtc -ne $webp.LastWriteTimeUtc) {
$webp.LastWriteTimeUtc = $oldDate
}
}
}
#}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment