ConvertTo-Gif.ps1
function ConvertTo-Gif { <# .Synopsis Converts an input video into an animated GIF .Description Converts an input video into a high-quality animated GIF .Example dir "$env:UserProfile\Videos\Too Many Cooks.mp4" | ConvertTo-Gif -Start "00:00:04.65" -End "00:00:06" -OutputPath "$env:UserProfile\Videos\Too Many Cooks.gif" .Link Get-Media .Link Join-Media .Link Convert-Media .Notes This really wouldn't have been possible without the great programmers who make ffmpeg. It was also greatly helped by a very diligent blogger who took the time to write down a detailed explanation of how FFMpeg works with animated GIFs. You can find that explanation here - http://blog.pkh.me/p/21-high-quality-gif-with-ffmpeg.html. #> [OutputType([IO.FileInfo], [Management.Automation.Job])] param( # The input path [Parameter(Mandatory,Position=0,ValueFromPipelineByPropertyName=$true,ValueFromPipeline=$true)] [Alias('Fullname')] [string] $InputPath, # The output path [Parameter(Mandatory,Position=1,ValueFromPipelineByPropertyName=$true)] [string] $OutputPath, # The path to FFMpeg.exe. Download it from http://ffmpeg.org/ [Parameter(ValueFromPipelineByPropertyName=$true)] [string] $FFMpegPath, # The timespan to start splitting the video [Parameter(Position=2, ValueFromPipelineByPropertyName=$true)] [Timespan] $Start, # The time span to end splitting the video [Parameter(Position=3, ValueFromPipelineByPropertyName=$true)] [Timespan] $End, # If set, will run this in a background job [Switch] $AsJob, # The number of frames per second. If not specified, this will match the existing framerate. [Uint32] $FrameRate, # The new width of the .gif [Uint32] $NewWidth, # If set, will use a difference-based palette. These put more focus on the motion than the background. [Switch] $DifferenceBasedPalette ) begin { $accumulatedArgs = [Collections.ArrayList]::new() } process { if ($AsJob) { . $StartRoughDraftJob return } $accumulatedArgs.AddRange(@(@{}+$PSBoundParameters)) } end { $c, $t, $id =0, $accumulatedArgs.Count, [Random]::new().Next() $ffMpeg = Get-FFMpeg -ffMpegPath $ffmpegPath if (-not $ffMpeg) { return } foreach ($accumulated in $accumulatedArgs) { foreach ($kv in $accumulated.GetEnumerator()) { $ExecutionContext.SessionState.PSVariable.Set($kv.Key, $kv.Value) } $ri = if ([IO.File]::Exists($InputPath)) { $InputPath } else { $ExecutionContext.SessionState.Path.GetResolvedPSPathFromPSPath($InputPath) | Get-Item -LiteralPath {$_ } | Select-Object -ExpandProperty Fullname } Write-Progress "Converting GIFs" "$ri " -PercentComplete ($c * 100 / $t) -Id $id $C++ $uro = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($OutputPath) $mi = Get-Media -InputPath $ri $ProgId = Get-Random if (-not $psBoundParameters.Start) { $start = [TimeSpan]::FromMilliseconds(0) } if (-not $PSBoundParameters.End) { $end = $mi.duration } $duration = $end - $start $PalettePath = Join-Path ([IO.Path]::GetTempPath()) "palette$(Get-Random).png" $FilterList = @() if ($FrameRate) { $FilterList+="fps=$FrameRate" } if ($NewWidth) { $FilterList+="scale=$($NewWidth):-1:flags=lanczos" } if ($FilterList) { $PaletteFilters = "`"$($FilterList -join ','),palettegen=stats_mode=$(if ($DifferenceBasedPalette) { 'diff'} else {'full'})`"" $paletteParams = @("-vf", $paletteFilters) $PaletteUseFilters = "`"$($FilterList -join ',') [x]; [x][1:v] paletteuse`"" $lavFiParams = @("-lavfi", $paletteUseFilters) } else { $PaletteFilters = '"palettegen"' $PaletteUseFilters = '"paletteuse"' $paletteParams = @("-vf", $paletteFilters) $lavFiParams = @("-lavfi", $paletteUseFilters) } #region Generate the palette Write-Progress "Generating Palette" " " -Id $ProgId -ParentId $id & $ffmpeg -ss "$start" -t "$($duration.TotalSeconds)" -i $ri @paletteParams "$palettePath" -y 2>&1 | ForEach-Object { Write-Verbose $_ } #endregion Generate the palette #region Convert to GIF & $ffmpeg -ss "$start" -t "$($duration.TotalSeconds)" -i $ri -i "$palettePath" @lavFiParams $uro -y 2>&1 | ForEach-Object { if ($_ -like "*time=*" -and $_ -like "*bitrate=*") { $lineChunks = $_.Tostring() -split "[ =]" -ne '' | Where-Object { $_.Trim() } $lineData = New-Object PSObject for ($i =0; $i -lt $lineChunks.Count; $i+=2) { $lineData |Add-Member NoteProperty $lineChunks[$i].TrimEnd("=") $lineChunks[$i + 1] -force } $time = $lineData.Time -as [Timespan] $perc = $time.TotalMilliseconds * 100 / $duration.TotalMilliseconds if ($perc -gt 100) { $perc = 100 } Write-Progress "Encoding" "$lineData".TrimStart("@{").TrimEnd("}") -PercentComplete $perc -id $progid -ParentId $id } else { Write-Verbose "$_" } } Write-Progress "Encoding Completed" " " -id $progid -Completed -ParentId $id Get-Item -Path $uro -ErrorAction SilentlyContinue #endregion Convert to GIF } Write-Progress "Converting GIFs" "Completed" -Completed -Id $id } } |