Skip to content

Instantly share code, notes, and snippets.

@emilwojcik93
Last active September 15, 2025 20:25
Show Gist options
  • Select an option

  • Save emilwojcik93/868fbc801667e65e334679fec62b6879 to your computer and use it in GitHub Desktop.

Select an option

Save emilwojcik93/868fbc801667e65e334679fec62b6879 to your computer and use it in GitHub Desktop.
VideoCompressor - PowerShell video compression tool optimized for cloud storage

VideoCompressor - Quick Start Guide

GitHub Gist: https://gist.github.com/emilwojcik93/868fbc801667e65e334679fec62b6879

TLDR - Two Simple Commands

Step 1: Download script to profile directory

$profileDir = Split-Path $PROFILE -Parent; if(!(Test-Path $profileDir)){md $profileDir -Force}; iwr 'https://gist.githubusercontent.com/emilwojcik93/868fbc801667e65e334679fec62b6879/raw/VideoCompressor.ps1' -OutFile "$profileDir\VideoCompressor.ps1" -UseBasicParsing

Step 2: Add to PowerShell profile

if(!(Select-String -Path $PROFILE -Pattern 'VideoCompressor.ps1' -Quiet -ErrorAction SilentlyContinue)){Add-Content -Path $PROFILE -Value "`n. '$((Split-Path $PROFILE -Parent))\VideoCompressor.ps1'" -Encoding UTF8}

Step 3: Restart PowerShell and use: Compress-Video "video.mp4"


Detailed Installation Options

Option 1: Install to PowerShell Profile (Recommended)

Standard installation command:

powershell.exe -ExecutionPolicy Bypass -Command "& {
    $profileDir = Split-Path `$PROFILE -Parent
    if (-not (Test-Path `$profileDir)) { New-Item -ItemType Directory -Path `$profileDir -Force }
    
    `$scriptUrl = 'https://gist.githubusercontent.com/emilwojcik93/868fbc801667e65e334679fec62b6879/raw/VideoCompressor.ps1'
    `$localPath = Join-Path `$profileDir 'VideoCompressor.ps1'
    
    Invoke-WebRequest -Uri `$scriptUrl -OutFile `$localPath -UseBasicParsing
    Add-Content -Path `$PROFILE -Value `"`n. ```"`$localPath```"`" -Encoding UTF8
    Write-Host 'VideoCompressor installed!' -ForegroundColor Green
}"

Option 2: Direct Execution (No Installation)

Run directly from gist without installing:

powershell.exe -ExecutionPolicy Bypass -Command "& ([ScriptBlock]::Create((irm 'https://gist.githubusercontent.com/emilwojcik93/868fbc801667e65e334679fec62b6879/raw/VideoCompressor.ps1'))); Compress-Video 'your-video.mp4'"

Option 3: Advanced with Parameters

powershell.exe -ExecutionPolicy Bypass -Command "
    $params = @{
        SourceVideo = 'C:\Users\$env:USERNAME\Downloads\video.mp4'
        CompressionMode = 'Quality'
        OutputDirectory = 'C:\Users\$env:USERNAME\OneDrive\CompressedVideos'
    }
    & ([ScriptBlock]::Create((irm 'https://gist.githubusercontent.com/emilwojcik93/868fbc801667e65e334679fec62b6879/raw/VideoCompressor.ps1')))
    Compress-Video @params
"

Quick Examples

# Basic compression (balanced mode)
Compress-Video "MyVideo.mp4"

# Fast processing
Compress-Video "MyVideo.mp4" -CompressionMode Fast

# Best quality
Compress-Video "MyVideo.mp4" -CompressionMode Quality

# Custom output folder
Compress-Video "MyVideo.mp4" -OutputDirectory "C:\Compressed"

What It Does

  • Auto-installs FFmpeg if not found
  • Hardware acceleration (Intel/NVIDIA)
  • Reduces file size by 60-80%
  • Maintains visual quality
  • Optimized for cloud storage
  • Works with all video formats

Requirements

  • Windows 10/11
  • PowerShell 5.1+
  • Internet connection (for FFmpeg auto-install)

Profile Installation Details

The two-step installation:

  1. Downloads script to $(Split-Path $PROFILE -Parent)\VideoCompressor.ps1
  2. Adds profile entry . 'path\to\VideoCompressor.ps1' to your $PROFILE
  3. Auto-loads when PowerShell starts

Profile Path Examples:

  • C:\Users\YourName\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
  • C:\Users\YourName\Documents\PowerShell\Microsoft.PowerShell_profile.ps1 (PowerShell 7+)

For Developers

  1. Clone this gist: git clone https://gist.github.com/868fbc801667e65e334679fec62b6879.git
  2. Install locally: Run .\Install-VideoCompressor.ps1
  3. Customize: Edit VideoCompressor.ps1 as needed

Perfect for preparing videos for OneDrive, YouTube, SharePoint, or any cloud platform!

# VideoCompressor - PowerShell Video Compression Tool
# Optimized for cloud storage platforms (OneDrive, YouTube, SharePoint, etc.)
function Test-FFmpegInstallation {
<#
.SYNOPSIS
Checks if FFmpeg is available and installs it if not found
#>
# Check if ffmpeg is available in PATH
$ffmpegAvailable = Get-Command ffmpeg -ErrorAction SilentlyContinue
$ffprobeAvailable = Get-Command ffprobe -ErrorAction SilentlyContinue
# If not found in PATH, check winget packages directory
if (-not $ffmpegAvailable -or -not $ffprobeAvailable) {
Write-Host "FFmpeg not found in PATH. Checking winget packages..." -ForegroundColor Yellow
$wingetPath = "$env:LOCALAPPDATA\Microsoft\WinGet\Packages"
if (Test-Path $wingetPath) {
# Look for FFmpeg packages
$ffmpegDirs = @(
"Gyan.FFmpeg_Microsoft.Winget.Source_8wekyb3d8bbwe",
"BtbN.FFmpeg*",
"*FFmpeg*"
)
foreach ($pattern in $ffmpegDirs) {
$packageDirs = Get-ChildItem -Path $wingetPath -Directory -Name $pattern -ErrorAction SilentlyContinue
foreach ($packageDir in $packageDirs) {
$fullPackagePath = Join-Path $wingetPath $packageDir
# Look for bin directories recursively
$binDirs = Get-ChildItem -Path $fullPackagePath -Recurse -Directory -Name "bin" -ErrorAction SilentlyContinue
foreach ($binDir in $binDirs) {
$binPath = Join-Path $fullPackagePath $binDir
$ffmpegExe = Join-Path $binPath "ffmpeg.exe"
$ffprobeExe = Join-Path $binPath "ffprobe.exe"
if ((Test-Path $ffmpegExe) -and (Test-Path $ffprobeExe)) {
Write-Host "Found FFmpeg installation at: $binPath" -ForegroundColor Green
# Add to current session PATH
if ($env:PATH -notlike "*$binPath*") {
$env:PATH += ";$binPath"
Write-Host "Added to current session PATH: $binPath" -ForegroundColor Green
}
# Ask user if they want to add to permanent PATH
$addToPermanentPath = Read-Host "Add FFmpeg to permanent system PATH? (y/n)"
if ($addToPermanentPath -eq 'y' -or $addToPermanentPath -eq 'Y') {
try {
# Get current user PATH
$userPath = [System.Environment]::GetEnvironmentVariable("PATH", "User")
if ($userPath -notlike "*$binPath*") {
$newUserPath = "$userPath;$binPath"
[System.Environment]::SetEnvironmentVariable("PATH", $newUserPath, "User")
Write-Host "Added to permanent user PATH. Restart PowerShell for changes to take effect." -ForegroundColor Green
} else {
Write-Host "FFmpeg is already in permanent PATH." -ForegroundColor Yellow
}
} catch {
Write-Host "Failed to update permanent PATH: $($_.Exception.Message)" -ForegroundColor Red
}
}
# Verify the commands are now available
$ffmpegAvailable = Get-Command ffmpeg -ErrorAction SilentlyContinue
$ffprobeAvailable = Get-Command ffprobe -ErrorAction SilentlyContinue
if ($ffmpegAvailable -and $ffprobeAvailable) {
Write-Host "FFmpeg is now available!" -ForegroundColor Green
return $true
}
}
}
}
}
}
}
if (-not $ffmpegAvailable -or -not $ffprobeAvailable) {
Write-Host "FFmpeg is not recognized as an internal or external command." -ForegroundColor Red
Write-Host "FFmpeg is required for video compression." -ForegroundColor Yellow
Write-Host ""
Write-Host "Available installation options via winget:" -ForegroundColor Cyan
Write-Host "1. Gyan.FFmpeg (MSVC build - Best Windows compatibility)" -ForegroundColor Green
Write-Host "2. BtbN.FFmpeg.GPL (MinGW build - Latest features)" -ForegroundColor Yellow
Write-Host ""
$install = Read-Host "Do you want to install FFmpeg now? (y/n)"
if ($install -eq 'y' -or $install -eq 'Y') {
Write-Host "Installing FFmpeg (Gyan.FFmpeg)..." -ForegroundColor Yellow
try {
winget install Gyan.FFmpeg --accept-package-agreements --accept-source-agreements
# Refresh PATH in current session
$env:PATH = [System.Environment]::GetEnvironmentVariable("PATH", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("PATH", "User")
# Verify installation
Start-Sleep -Seconds 3
$ffmpegAvailable = Get-Command ffmpeg -ErrorAction SilentlyContinue
if (-not $ffmpegAvailable) {
Write-Host "Primary installation failed. Trying alternative package (BtbN.FFmpeg.GPL)..." -ForegroundColor Yellow
winget install BtbN.FFmpeg.GPL --accept-package-agreements --accept-source-agreements
# Refresh PATH again
$env:PATH = [System.Environment]::GetEnvironmentVariable("PATH", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("PATH", "User")
Start-Sleep -Seconds 3
$ffmpegAvailable = Get-Command ffmpeg -ErrorAction SilentlyContinue
}
if ($ffmpegAvailable) {
Write-Host "FFmpeg installed successfully!" -ForegroundColor Green
Write-Host "You may need to restart PowerShell for PATH changes to take effect." -ForegroundColor Yellow
return $true
}
else {
Write-Host "FFmpeg installation failed. Please install manually or restart PowerShell." -ForegroundColor Red
Write-Host "Manual installation: Download from https://ffmpeg.org/download.html" -ForegroundColor Yellow
return $false
}
}
catch {
Write-Host "Error installing FFmpeg: $($_.Exception.Message)" -ForegroundColor Red
Write-Host "Please try installing manually:" -ForegroundColor Yellow
Write-Host " winget install Gyan.FFmpeg" -ForegroundColor Cyan
Write-Host " or: winget install BtbN.FFmpeg.GPL" -ForegroundColor Cyan
return $false
}
}
else {
Write-Host "FFmpeg installation cancelled. Cannot proceed without FFmpeg." -ForegroundColor Red
return $false
}
}
return $true
}
function Get-VideoProperties {
<#
.SYNOPSIS
Uses ffprobe to analyze video properties for optimal compression
#>
param(
[Parameter(Mandatory = $true)]
[string]$VideoPath
)
try {
# Get video information using ffprobe
$probeOutput = ffprobe -v quiet -print_format json -show_streams -show_format "$VideoPath" | ConvertFrom-Json
$videoStream = $probeOutput.streams | Where-Object { $_.codec_type -eq "video" } | Select-Object -First 1
$audioStream = $probeOutput.streams | Where-Object { $_.codec_type -eq "audio" } | Select-Object -First 1
$properties = @{
Duration = [math]::Round([double]$probeOutput.format.duration, 2)
Width = [int]$videoStream.width
Height = [int]$videoStream.height
FrameRate = if ($videoStream.r_frame_rate) {
$fps = $videoStream.r_frame_rate.Split('/')
[math]::Round([double]$fps[0] / [double]$fps[1], 2)
} else { 30 }
VideoBitrate = if ($videoStream.bit_rate) { [int]$videoStream.bit_rate } else { 0 }
AudioBitrate = if ($audioStream.bit_rate) { [int]$audioStream.bit_rate } else { 128000 }
VideoCodec = $videoStream.codec_name
AudioCodec = if ($audioStream) { $audioStream.codec_name } else { "none" }
FileSize = [math]::Round((Get-Item $VideoPath).Length / 1MB, 2)
}
return $properties
}
catch {
Write-Host "Error analyzing video: $($_.Exception.Message)" -ForegroundColor Red
return $null
}
}
function Get-OptimalCompressionSettings {
<#
.SYNOPSIS
Determines optimal compression settings based on video properties and target platform
#>
param(
[Parameter(Mandatory = $true)]
[hashtable]$VideoProperties,
[Parameter(Mandatory = $false)]
[string]$CompressionMode = "Balanced"
)
$width = $VideoProperties.Width
$height = $VideoProperties.Height
$fps = $VideoProperties.FrameRate
$duration = $VideoProperties.Duration
# Determine target resolution (maintain aspect ratio)
$targetWidth = $width
$targetHeight = $height
# Optimize for cloud storage - limit resolution for better streaming
if ($width -gt 1920 -or $height -gt 1080) {
# Scale down to 1080p max while maintaining aspect ratio
$aspectRatio = $width / $height
if ($aspectRatio -gt (16/9)) {
$targetWidth = 1920
$targetHeight = [math]::Round(1920 / $aspectRatio / 2) * 2 # Even number
} else {
$targetHeight = 1080
$targetWidth = [math]::Round(1080 * $aspectRatio / 2) * 2 # Even number
}
}
# Optimize frame rate
$targetFps = $fps
if ($fps -gt 60) { $targetFps = 60 }
elseif ($fps -gt 30 -and $duration -gt 600) { $targetFps = 30 } # Long videos benefit from 30fps
# Determine CRF based on content type and duration
$crf = 23 # Default balanced quality
if ($duration -lt 300) { $crf = 21 } # Short videos - higher quality
elseif ($duration -gt 3600) { $crf = 25 } # Long videos - more compression
# Audio bitrate optimization
$audioBitrate = "128k"
if ($VideoProperties.AudioBitrate -gt 0) {
if ($VideoProperties.AudioBitrate -le 96000) { $audioBitrate = "96k" }
elseif ($VideoProperties.AudioBitrate -ge 256000) { $audioBitrate = "192k" }
}
# Choose preset and CRF based on compression mode
switch ($CompressionMode) {
"Fast" {
$preset = "fast"
$crf = $crf + 2 # Slightly higher CRF for faster encoding
}
"Quality" {
$preset = "slow"
$crf = $crf - 1 # Lower CRF for better quality
}
default { # "Balanced"
$preset = "medium"
# Keep calculated CRF as is
}
}
# Try to detect hardware acceleration capabilities (only if ffmpeg is available)
$hwEncoder = $null
$hwAvailable = $false
if (Get-Command ffmpeg -ErrorAction SilentlyContinue) {
# Check for Intel QuickSync (common on Intel CPUs like Core Ultra)
try {
$null = ffmpeg -hide_banner -f lavfi -i testsrc2=duration=1:size=320x240:rate=1 -c:v h264_qsv -f null - 2>$null
if ($LASTEXITCODE -eq 0) {
$hwEncoder = "h264_qsv"
$hwAvailable = $true
}
} catch { }
# Check for NVIDIA NVENC if QuickSync not available
if (-not $hwAvailable) {
try {
$null = ffmpeg -hide_banner -f lavfi -i testsrc2=duration=1:size=320x240:rate=1 -c:v h264_nvenc -f null - 2>$null
if ($LASTEXITCODE -eq 0) {
$hwEncoder = "h264_nvenc"
$hwAvailable = $true
}
} catch { }
}
}
$settings = @{
Resolution = "${targetWidth}x${targetHeight}"
FrameRate = $targetFps
CRF = $crf
AudioBitrate = $audioBitrate
Preset = $preset
Profile = "high"
Level = "4.0"
HardwareEncoder = $hwEncoder
HardwareAvailable = $hwAvailable
}
return $settings
}
function Get-OutputDirectory {
<#
.SYNOPSIS
Determines the best output directory (OneDrive or Videos folder)
#>
# Check if OneDrive is available and service is running
$oneDriveAvailable = $false
if ($env:OneDrive -and (Test-Path $env:OneDrive)) {
# Check if OneDrive service is running
$oneDriveService = Get-Process -Name "OneDrive" -ErrorAction SilentlyContinue
if ($oneDriveService) {
$oneDriveAvailable = $true
$outputDir = Join-Path $env:OneDrive "CompressedVideos"
}
}
if (-not $oneDriveAvailable) {
# Fallback to Videos directory using .NET method
try {
$videosPath = [System.Environment]::GetFolderPath('MyVideos')
$outputDir = Join-Path $videosPath "CompressedVideos"
}
catch {
# Final fallback to user profile
$outputDir = Join-Path $env:USERPROFILE "Videos\CompressedVideos"
}
}
# Create directory if it doesn't exist
if (-not (Test-Path $outputDir)) {
New-Item -ItemType Directory -Path $outputDir -Force | Out-Null
Write-Host "Created output directory: $outputDir" -ForegroundColor Green
}
return $outputDir
}
function Compress-Video {
<#
.SYNOPSIS
Compresses video files with optimal settings for cloud storage platforms
.DESCRIPTION
This function compresses video files using FFmpeg with settings optimized for
cloud storage platforms like OneDrive, YouTube, SharePoint, and MS Stream.
.PARAMETER SourceVideo
Path to the source video file to compress
.PARAMETER OutputDirectory
Optional custom output directory. If not specified, uses OneDrive or Videos folder
.PARAMETER CompressionMode
Choose compression speed/quality balance: Fast, Balanced, Quality
- Fast: Quick processing, good quality (faster than real-time)
- Balanced: Good balance of speed and compression (default)
- Quality: Best compression, slower processing
.EXAMPLE
Compress-Video "C:\Videos\MyVideo.mp4"
.EXAMPLE
Compress-Video -SourceVideo ".\Local Development Environment Setup Guide.mp4" -CompressionMode Fast
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true, Position = 0)]
[string]$SourceVideo,
[Parameter(Mandatory = $false)]
[string]$OutputDirectory,
[Parameter(Mandatory = $false)]
[ValidateSet("Fast", "Balanced", "Quality")]
[string]$CompressionMode = "Balanced"
)
# Check if FFmpeg is available
if (-not (Test-FFmpegInstallation)) {
return
}
# Validate source file
if (-not (Test-Path $SourceVideo)) {
Write-Host "Error: Source video file not found: $SourceVideo" -ForegroundColor Red
return
}
Write-Host "Analyzing video properties..." -ForegroundColor Yellow
$videoProps = Get-VideoProperties -VideoPath $SourceVideo
if (-not $videoProps) {
Write-Host "Failed to analyze video properties." -ForegroundColor Red
return
}
# Display video information
Write-Host "`nVideo Information:" -ForegroundColor Cyan
Write-Host "Duration: $($videoProps.Duration) seconds"
Write-Host "Resolution: $($videoProps.Width)x$($videoProps.Height)"
Write-Host "Frame Rate: $($videoProps.FrameRate) fps"
Write-Host "File Size: $($videoProps.FileSize) MB"
Write-Host "Video Codec: $($videoProps.VideoCodec)"
Write-Host "Audio Codec: $($videoProps.AudioCodec)"
# Get optimal compression settings
$compressionSettings = Get-OptimalCompressionSettings -VideoProperties $videoProps -CompressionMode $CompressionMode
Write-Host "`nOptimal Compression Settings:" -ForegroundColor Cyan
Write-Host "Compression Mode: $CompressionMode"
Write-Host "Target Resolution: $($compressionSettings.Resolution)"
Write-Host "Target Frame Rate: $($compressionSettings.FrameRate) fps"
Write-Host "CRF Quality: $($compressionSettings.CRF)"
Write-Host "Audio Bitrate: $($compressionSettings.AudioBitrate)"
Write-Host "Preset: $($compressionSettings.Preset)"
if ($compressionSettings.HardwareAvailable) {
Write-Host "Hardware Encoder: $($compressionSettings.HardwareEncoder) (faster processing)" -ForegroundColor Green
} else {
Write-Host "Hardware Encoder: Software only (h264)" -ForegroundColor Yellow
}
# Determine output directory and file path
if (-not $OutputDirectory) {
$OutputDirectory = Get-OutputDirectory
}
$sourceFileName = [System.IO.Path]::GetFileNameWithoutExtension($SourceVideo)
$outputFileName = "${sourceFileName}_compressed.mp4"
$outputPath = Join-Path $OutputDirectory $outputFileName
# Check if output file already exists
if (Test-Path $outputPath) {
$counter = 1
do {
$outputFileName = "${sourceFileName}_compressed_${counter}.mp4"
$outputPath = Join-Path $OutputDirectory $outputFileName
$counter++
} while (Test-Path $outputPath)
}
Write-Host "`nStarting compression..." -ForegroundColor Green
Write-Host "Output will be saved to: $outputPath" -ForegroundColor Yellow
# Build FFmpeg command with optimal settings
if ($compressionSettings.HardwareAvailable -and $CompressionMode -ne "Quality") {
# Use hardware encoder for faster processing (except in Quality mode)
$videoCodec = $compressionSettings.HardwareEncoder
# Calculate target bitrate based on resolution and frame rate
$pixelCount = ($compressionSettings.Resolution -split 'x')[0] * ($compressionSettings.Resolution -split 'x')[1]
$targetBitrate = [math]::Round(($pixelCount * $compressionSettings.FrameRate * 0.1) / 1000) # Rough estimate
if ($targetBitrate -lt 1000) { $targetBitrate = 1000 }
if ($targetBitrate -gt 8000) { $targetBitrate = 8000 }
$ffmpegArgs = @(
"-i", "`"$SourceVideo`""
"-c:v", $videoCodec
"-b:v", "${targetBitrate}k"
"-maxrate", "$([math]::Round($targetBitrate * 1.5))k"
"-bufsize", "$([math]::Round($targetBitrate * 2))k"
"-vf", "scale=$($compressionSettings.Resolution)"
"-r", $compressionSettings.FrameRate
"-c:a", "aac"
"-b:a", $compressionSettings.AudioBitrate
"-movflags", "+faststart"
"-avoid_negative_ts", "make_zero"
"-fflags", "+genpts"
"-y"
"`"$outputPath`""
)
} else {
# Use software encoder with CRF
$ffmpegArgs = @(
"-i", "`"$SourceVideo`""
"-c:v", "libx264"
"-preset", $compressionSettings.Preset
"-crf", $compressionSettings.CRF
"-profile:v", $compressionSettings.Profile
"-level", $compressionSettings.Level
"-vf", "scale=$($compressionSettings.Resolution)"
"-r", $compressionSettings.FrameRate
"-c:a", "aac"
"-b:a", $compressionSettings.AudioBitrate
"-movflags", "+faststart"
"-avoid_negative_ts", "make_zero"
"-fflags", "+genpts"
"-y"
"`"$outputPath`""
)
}
try {
$startTime = Get-Date
Write-Host "Executing: ffmpeg $($ffmpegArgs -join ' ')" -ForegroundColor Gray
# Execute FFmpeg command
& ffmpeg @ffmpegArgs
$endTime = Get-Date
$processingTime = ($endTime - $startTime).TotalSeconds
if (Test-Path $outputPath) {
$outputFileSize = [math]::Round((Get-Item $outputPath).Length / 1MB, 2)
$compressionRatio = [math]::Round((1 - ($outputFileSize / $videoProps.FileSize)) * 100, 1)
Write-Host "`nCompression completed successfully!" -ForegroundColor Green
Write-Host "Processing time: $([math]::Round($processingTime, 1)) seconds"
Write-Host "Original size: $($videoProps.FileSize) MB"
Write-Host "Compressed size: $outputFileSize MB"
Write-Host "Compression ratio: $compressionRatio%"
Write-Host "Output location: $outputPath"
# Open output directory
$openDir = Read-Host "`nOpen output directory? (y/n)"
if ($openDir -eq 'y' -or $openDir -eq 'Y') {
Start-Process "explorer.exe" -ArgumentList $OutputDirectory
}
}
else {
Write-Host "Compression failed - output file not found." -ForegroundColor Red
}
}
catch {
Write-Host "Error during compression: $($_.Exception.Message)" -ForegroundColor Red
}
}
# Export the main function (only when loaded as a module)
if ($MyInvocation.MyCommand.Path -and $MyInvocation.MyCommand.Path.EndsWith('.psm1')) {
Export-ModuleMember -Function Compress-Video
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment