207 lines
9.5 KiB
PowerShell
207 lines
9.5 KiB
PowerShell
# SMART PRE-FLIGHT ENCODER - VERBOSE CONSOLE OUTPUT
|
|
# Usage: .\Encode-TVShows.ps1
|
|
|
|
param(
|
|
[string]$TvDir = "Z:\tv",
|
|
[string]$ContentDir = "Z:\content",
|
|
[int]$MaxJobs = 2,
|
|
[int]$Av1Q = 34,
|
|
[int]$HevcQ = 28,
|
|
[string]$EncoderAV1 = "av1_amf",
|
|
[string]$EncoderHEVC = "hevc_amf",
|
|
[switch]$SkipAV1 = $true
|
|
)
|
|
|
|
# --- CONFIGURATION ---
|
|
$Global:FFMPEG = "ffmpeg"
|
|
$Global:FFPROBE = "ffprobe"
|
|
$Global:TEMP_DIR = "C:\Users\bnair\Videos\encodes"
|
|
$Global:LockDir = "C:\Users\bnair\Videos\encodes\locks"
|
|
$LogFileTV = "C:\Users\bnair\Videos\encodes\encoding-log-tv.csv"
|
|
$LogFileContent = "C:\Users\bnair\Videos\encodes\encoding-log-content.csv"
|
|
|
|
if (-not (Test-Path $Global:TEMP_DIR)) { New-Item -ItemType Directory -Path $Global:TEMP_DIR -Force | Out-Null }
|
|
if (-not (Test-Path $Global:LockDir)) { New-Item -ItemType Directory -Path $Global:LockDir -Force | Out-Null }
|
|
|
|
function Init-LogFile {
|
|
param([string]$Path)
|
|
if (-not (Test-Path $Path)) { "Timestamp,File,InputSize,OutputSize,CodecUsed,Status,Savings" | Out-File -FilePath $Path -Encoding UTF8 }
|
|
}
|
|
Init-LogFile $LogFileTV
|
|
Init-LogFile $LogFileContent
|
|
|
|
function Test-Tools {
|
|
Write-Host "Setup: Checking required tools..." -ForegroundColor Cyan
|
|
if (-not (Get-Command $Global:FFMPEG -ErrorAction SilentlyContinue)) { Write-Host "ERROR: ffmpeg not found!" -ForegroundColor Red; exit 1 }
|
|
Write-Host "Setup: Tools found." -ForegroundColor Green
|
|
}
|
|
|
|
$SharedFunctions = {
|
|
function Get-LockId {
|
|
param([string]$FilePath)
|
|
try {
|
|
$pathBytes = [System.Text.Encoding]::UTF8.GetBytes($FilePath)
|
|
$hash = [System.Security.Cryptography.SHA256]::Create().ComputeHash($pathBytes)
|
|
return [BitConverter]::ToString($hash).Replace("-", "").Substring(0, 16)
|
|
} catch { return "unknown_lock" }
|
|
}
|
|
|
|
function Run-FFmpeg {
|
|
param($In, $Out, $Enc, $Q, $Seek=$null, $Duration=$null)
|
|
$argsList = "-hide_banner -loglevel error -y"
|
|
if ($Seek) { $argsList += " -ss $Seek" }
|
|
$argsList += " -i `"$In`""
|
|
if ($Duration) { $argsList += " -t $Duration" }
|
|
$argsList += " -c:v $Enc -usage transcoding -quality quality -rc cqp -qp_i $Q -qp_p $Q -qp_b $Q -c:a copy `"$Out`""
|
|
return Start-Process -FilePath "ffmpeg" -ArgumentList $argsList -NoNewWindow -Wait -PassThru
|
|
}
|
|
|
|
function Process-VideoFile {
|
|
param($InputFile, $CurrentLogFile, $LockDir, $TempDir, $Av1Q, $HevcQ, $EncAV1, $EncHEVC, $SkipAV1)
|
|
|
|
$pidStr = $PID.ToString()
|
|
$lockId = Get-LockId -FilePath $InputFile
|
|
$lockFile = Join-Path $LockDir "$lockId.lock"
|
|
|
|
try {
|
|
if (Test-Path $lockFile) { return }
|
|
$pidStr | Out-File -FilePath $lockFile -Force
|
|
|
|
$fileName = Split-Path $InputFile -Leaf
|
|
Write-Host "[$pidStr] Found: $fileName" -ForegroundColor White
|
|
|
|
# Skip Logic
|
|
$currentCodec = (& "ffprobe" -v error -select_streams v:0 -show_entries stream=codec_name -of csv=p=0 "$InputFile" 2>&1)
|
|
if ($SkipAV1 -and ($currentCodec -match "av1" -or $currentCodec -match "hevc")) {
|
|
Write-Host "[$pidStr] SKIP: Already optimized ($currentCodec)" -ForegroundColor DarkGray
|
|
return
|
|
}
|
|
|
|
$inputSize = (Get-Item $InputFile).Length
|
|
|
|
# --- PHASE 1: PRE-FLIGHT SAMPLE ---
|
|
Write-Host "[$pidStr] Testing: Generating 60s sample..." -ForegroundColor Yellow
|
|
$tempSample = Join-Path $TempDir "$fileName.sample.mkv"
|
|
$procSample = Run-FFmpeg $InputFile $tempSample $EncAV1 $Av1Q "00:05:00" "60"
|
|
|
|
$doFullAV1 = $true
|
|
|
|
if ($procSample.ExitCode -eq 0 -and (Test-Path $tempSample)) {
|
|
$sampleSize = (Get-Item $tempSample).Length
|
|
# Threshold: 150MB for 60s is ~20Mbps (Likely bloat)
|
|
if ($sampleSize -gt 150MB) {
|
|
Write-Host "[$pidStr] Test Result: FAIL. Sample was $([math]::Round($sampleSize/1MB))MB. Too big for AV1." -ForegroundColor Red
|
|
$doFullAV1 = $false
|
|
} else {
|
|
Write-Host "[$pidStr] Test Result: PASS. Sample was $([math]::Round($sampleSize/1MB))MB. Proceeding with AV1." -ForegroundColor Green
|
|
}
|
|
Remove-Item $tempSample -Force
|
|
}
|
|
|
|
$finalStatus = "Failed"
|
|
$finalCodec = "None"
|
|
$finalSize = 0
|
|
$finalSavings = 0.00
|
|
|
|
# --- PHASE 2: FULL AV1 ---
|
|
if ($doFullAV1) {
|
|
Write-Host "[$pidStr] Action: Starting Full AV1 Encode..." -ForegroundColor Cyan
|
|
$tempAV1 = Join-Path $TempDir "$fileName.av1.mkv"
|
|
$procAV1 = Run-FFmpeg $InputFile $tempAV1 $EncAV1 $Av1Q
|
|
|
|
if ($procAV1.ExitCode -eq 0 -and (Test-Path $tempAV1)) {
|
|
$sizeAV1 = (Get-Item $tempAV1).Length
|
|
|
|
if ($sizeAV1 -lt $inputSize) {
|
|
$finalSavings = [math]::Round((1 - ($sizeAV1 / $inputSize)) * 100, 2)
|
|
Write-Host "[$pidStr] AV1 ACCEPTED: Saved ${finalSavings}%" -ForegroundColor Green
|
|
|
|
$finalOut = $InputFile -replace '\.mkv$', '_av1.mkv' -replace '\.mp4$', '_av1.mp4'
|
|
Move-Item $tempAV1 -Destination $finalOut -Force
|
|
Remove-Item $InputFile -Force
|
|
Rename-Item $finalOut -NewName $fileName -Force
|
|
|
|
"$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss'),`"$InputFile`",$inputSize,$sizeAV1,AV1,`"Replaced (AV1)`",$finalSavings%" | Out-File -FilePath $CurrentLogFile -Append -Encoding UTF8
|
|
return
|
|
} else {
|
|
Write-Host "[$pidStr] AV1 REJECTED: Larger than source ($([math]::Round($sizeAV1/1GB,2)) GB). Deleting..." -ForegroundColor Red
|
|
Remove-Item $tempAV1 -Force
|
|
}
|
|
}
|
|
}
|
|
|
|
# --- PHASE 3: HEVC FALLBACK ---
|
|
Write-Host "[$pidStr] Action: Trying HEVC Fallback..." -ForegroundColor Cyan
|
|
$tempHEVC = Join-Path $TempDir "$fileName.hevc.mkv"
|
|
$procHEVC = Run-FFmpeg $InputFile $tempHEVC $EncHEVC $HevcQ
|
|
|
|
if ($procHEVC.ExitCode -eq 0 -and (Test-Path $tempHEVC)) {
|
|
$sizeHEVC = (Get-Item $tempHEVC).Length
|
|
|
|
if ($sizeHEVC -lt $inputSize) {
|
|
$finalSavings = [math]::Round((1 - ($sizeHEVC / $inputSize)) * 100, 2)
|
|
Write-Host "[$pidStr] HEVC ACCEPTED: Saved ${finalSavings}%" -ForegroundColor Green
|
|
|
|
$finalOut = $InputFile -replace '\.mkv$', '_hevc.mkv' -replace '\.mp4$', '_hevc.mp4'
|
|
Move-Item $tempHEVC -Destination $finalOut -Force
|
|
Remove-Item $InputFile -Force
|
|
Rename-Item $finalOut -NewName $fileName -Force
|
|
|
|
$finalStatus = "Replaced (HEVC)"
|
|
$finalCodec = "HEVC"
|
|
$finalSize = $sizeHEVC
|
|
} else {
|
|
Write-Host "[$pidStr] HEVC REJECTED: Also larger. Keeping original." -ForegroundColor Red
|
|
Remove-Item $tempHEVC -Force
|
|
$finalStatus = "Rejected (Both Larger)"
|
|
$finalSize = $sizeHEVC
|
|
$finalCodec = "HEVC"
|
|
}
|
|
}
|
|
|
|
"$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss'),`"$InputFile`",$inputSize,$finalSize,$finalCodec,`"$finalStatus`",$finalSavings%" | Out-File -FilePath $CurrentLogFile -Append -Encoding UTF8
|
|
|
|
} finally {
|
|
Remove-Item $lockFile -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
}
|
|
|
|
function Process-Directory {
|
|
param($TargetDirectory, $TargetLogFile, $PhaseName)
|
|
if (-not (Test-Path $TargetDirectory)) { return }
|
|
|
|
Write-Host "`n=== PHASE: $PhaseName ===" -ForegroundColor Magenta
|
|
$files = Get-ChildItem -Path $TargetDirectory -Include *.mkv, *.mp4 -Recurse -File -ErrorAction SilentlyContinue
|
|
$videoFiles = $files | Where-Object { $_.Name -notmatch "_av1" -and $_.Name -notmatch "_hevc" }
|
|
|
|
Write-Host "Found $($videoFiles.Count) files." -ForegroundColor Cyan
|
|
|
|
$processed = 0
|
|
while ($processed -lt $videoFiles.Count) {
|
|
$batchSize = [math]::Min($MaxJobs, ($videoFiles.Count - $processed))
|
|
$currentBatch = $videoFiles[$processed..($processed + $batchSize - 1)]
|
|
$jobs = @()
|
|
|
|
foreach ($file in $currentBatch) {
|
|
$jobs += Start-Job -InitializationScript $SharedFunctions -ScriptBlock {
|
|
param($f, $log, $lock, $temp, $av1q, $hevcq, $e1, $e2, $skip)
|
|
Process-VideoFile $f $log $lock $temp $av1q $hevcq $e1 $e2 $skip
|
|
} -ArgumentList $file.FullName, $TargetLogFile, $Global:LockDir, $Global:TEMP_DIR, $Av1Q, $HevcQ, $EncoderAV1, $EncoderHEVC, $SkipAV1
|
|
}
|
|
|
|
while (($jobs | Where-Object { $_.State -eq 'Running' }).Count -gt 0) {
|
|
$jobs | Receive-Job
|
|
Start-Sleep -Seconds 2
|
|
}
|
|
$jobs | Receive-Job
|
|
$jobs | Remove-Job -Force
|
|
$processed += $batchSize
|
|
Write-Host "Progress Phase ${PhaseName}: $processed / $($videoFiles.Count)" -ForegroundColor Yellow
|
|
}
|
|
}
|
|
|
|
Test-Tools
|
|
Process-Directory $TvDir $LogFileTV "TV-Shows"
|
|
Process-Directory $ContentDir $LogFileContent "Content"
|
|
Write-Host "`nDone." -ForegroundColor Green |