|
# ================================================================================ |
|
# === Telegram Emoji Converter === |
|
# ================================================================================ |
|
|
|
# --- Configuration --- |
|
$maxParallelJobs = 50 |
|
$EnableBenchmark = $true |
|
$GpuPreference = "none" # "auto", "nvidia", "amd", "intel", "none" |
|
# --- End of Configuration --- |
|
|
|
# --- Get Location --- |
|
$CurrentFolder = $PWD.Path |
|
$ParentFolder = Split-Path -Parent $CurrentFolder |
|
|
|
# --- Prerequisite Check --- |
|
$moduleFolderPath = Join-Path $ParentFolder -ChildPath "threads\microsoft.powershell.threadjob" |
|
if (Test-Path $moduleFolderPath) { |
|
Get-ChildItem -Path $moduleFolderPath -Recurse | Unblock-File -ErrorAction SilentlyContinue |
|
$manifestPath = Get-ChildItem -Path $moduleFolderPath -Filter "*.psd1" | Select-Object -First 1 |
|
if ($manifestPath) { |
|
$module = Import-Module -Name $manifestPath.FullName -PassThru -ErrorAction SilentlyContinue |
|
if (-not $module) { Write-Error "Module failed to import."; Read-Host; exit } |
|
} else { Write-Error "No .psd1 file found."; Read-Host; exit } |
|
} else { Write-Error "Module folder not found."; Read-Host; exit } |
|
|
|
$ffmpegPath = Join-Path $ParentFolder "ffmpeg.exe" |
|
$ffprobePath = Join-Path $ParentFolder "ffprobe.exe" |
|
if (-not (Test-Path $ffmpegPath)) { Write-Error "[FATAL] ffmpeg.exe not found."; Read-Host; exit } |
|
if (-not (Test-Path $ffprobePath)) { Write-Error "[FATAL] ffprobe.exe not found."; Read-Host; exit } |
|
|
|
# --- GPU Auto-Detection Logic --- |
|
# SEEMS NOT SUPPORTED AND UNNECESARY |
|
$videoEncoder = "libvpx-vp9"; $hwaccel_params = @(); $gpuDetected = "CPU (High Quality)" |
|
if ($GpuPreference -ne "none") { |
|
$gpuInfo = (Get-WmiObject Win32_VideoController).Name; $vendor = $GpuPreference |
|
if ($vendor -eq "auto") { |
|
if ($gpuInfo -like "*NVIDIA*") { $vendor = "nvidia" } |
|
elseif ($gpuInfo -like "*AMD*" -or $gpuInfo -like "*Radeon*") { $vendor = "amd" } |
|
elseif ($gpuInfo -like "*Intel*") { $vendor = "intel" } |
|
} |
|
switch ($vendor) { |
|
"nvidia" { $videoEncoder = "vp9_nvenc"; $hwaccel_params = @('-hwaccel', 'cuda'); $gpuDetected = "NVIDIA (NVENC)" } |
|
"amd" { $videoEncoder = "vp9_amf"; $hwaccel_params = @('-hwaccel', 'd3d11va'); $gpuDetected = "AMD (AMF)" } |
|
"intel" { $videoEncoder = "vp9_qsv"; $hwaccel_params = @('-hwaccel', 'qsv'); $gpuDetected = "Intel (QSV)" } |
|
} |
|
} |
|
|
|
# --- Initial Setup --- |
|
$videoOutputDir = Join-Path $CurrentFolder "output_video" |
|
$staticOutputDir = Join-Path $CurrentFolder "output_static" |
|
New-Item -ItemType Directory -Path $videoOutputDir -Force | Out-Null |
|
New-Item -ItemType Directory -Path $staticOutputDir -Force | Out-Null |
|
|
|
# --- Main Logic Block --- |
|
Write-Host "Detected GPU for acceleration: $gpuDetected" -ForegroundColor Green |
|
if ($EnableBenchmark) { Write-Host "Benchmark Timer: Enabled" -ForegroundColor Yellow } |
|
Write-Host "=====================================================================" |
|
|
|
$conversionLogic = { |
|
$allFiles = Get-ChildItem -Path $CurrentFolder -Filter "*.avif" -Recurse | Where-Object { |
|
$_.FullName -notlike "*$videoOutputDir\*" -and $_.FullName -notlike "*$staticOutputDir\*" |
|
} |
|
if ($allFiles.Count -eq 0) { Write-Warning "No .avif files found."; return } |
|
|
|
$initializationScript = { |
|
function Set-WebmDuration { |
|
param( |
|
[string]$FilePath, |
|
[double]$SpoofNumber = 555.55 |
|
) |
|
try { |
|
$bytes = $null |
|
for ($attempt = 1; $attempt -le 5; $attempt++) { |
|
try { |
|
$bytes = [System.IO.File]::ReadAllBytes($FilePath) |
|
if ($bytes -ne $null -and $bytes.Length -gt 100) { break } |
|
} catch {} |
|
Start-Sleep -Milliseconds 50 |
|
} |
|
if ($null -eq $bytes) { return "Failed to read file." } |
|
|
|
$infoId = [byte[]](0x15, 0x49, 0xA9, 0x66) |
|
$durationId = [byte[]](0x44, 0x89) |
|
|
|
$infoOffset = -1 |
|
for ($i = 0; $i -lt ($bytes.Length - $infoId.Length); $i++) { |
|
$match = $true |
|
for ($j = 0; $j -lt $infoId.Length; $j++) { if ($bytes[$i + $j] -ne $infoId[$j]) { $match = $false; break } } |
|
if ($match) { $infoOffset = $i; break } |
|
} |
|
if ($infoOffset -eq -1) { return "Info tag not found" } |
|
|
|
$durationOffset = -1 |
|
$searchLimit = [System.Math]::Min($infoOffset + 200, $bytes.Length) |
|
for ($i = $infoOffset; $i -lt ($searchLimit - $durationId.Length); $i++) { |
|
$match = $true |
|
for ($j = 0; $j -lt $durationId.Length; $j++) { if ($bytes[$i + $j] -ne $durationId[$j]) { $match = $false; break } } |
|
if ($match) { $durationOffset = $i; break } |
|
} |
|
if ($durationOffset -eq -1) { return "Duration tag not found after Info tag" } |
|
|
|
$sizeByteOffset = $durationOffset + $durationId.Length |
|
if ($sizeByteOffset -ge $bytes.Length) { return "File is truncated; cannot read size byte."} |
|
$sizeByte = $bytes[$sizeByteOffset] |
|
$targetLength = 0 |
|
if ($sizeByte -eq 0x84) { $targetLength = 4 } |
|
elseif ($sizeByte -eq 0x88) { $targetLength = 8 } |
|
else { return "Unsupported duration size marker: $($sizeByte)" } |
|
|
|
$newDurationBytes = @() |
|
if ($targetLength -eq 4) { $newDurationBytes = [System.BitConverter]::GetBytes([float]$SpoofNumber) } |
|
else { $newDurationBytes = [System.BitConverter]::GetBytes([double]$SpoofNumber) } |
|
if ([System.BitConverter]::IsLittleEndian) { [System.Array]::Reverse($newDurationBytes) } |
|
|
|
$valueOffset = $sizeByteOffset + 1 |
|
[System.Array]::Copy($newDurationBytes, 0, $bytes, $valueOffset, $targetLength) |
|
[System.IO.File]::WriteAllBytes($FilePath, $bytes) |
|
return $true |
|
} catch { |
|
return "ERROR: $($_.Exception.Message)" |
|
} |
|
} |
|
} |
|
|
|
$jobs = $allFiles | ForEach-Object { |
|
Start-ThreadJob -ThrottleLimit $maxParallelJobs -InitializationScript $initializationScript -ScriptBlock { |
|
param( |
|
$file, $ffmpegPath, $ffprobePath, $videoOutputDir, $staticOutputDir, |
|
$hwaccel_args, $videoEncoder |
|
) |
|
|
|
$baseName = $file.BaseName; $parentFolderName = $file.Directory.Name |
|
|
|
if ($parentFolderName -eq "Static") { |
|
$streamCount = & $ffprobePath -v error -select_streams v -show_entries format=nb_streams -of default=noprint_wrappers=1:nokey=1 $file.FullName |
|
$targetPath = Join-Path -Path $staticOutputDir -ChildPath "$baseName.png" |
|
$counter = 1; while (Test-Path $targetPath) { $targetPath = Join-Path -Path $staticOutputDir -ChildPath "$baseName-$counter.png"; $counter++ } |
|
if ($streamCount -ge 2) { $filterComplex = "[0:0][0:1]alphamerge,scale=w=100:h=100:force_original_aspect_ratio=decrease,pad=w=100:h=100:x=(100-iw)/2:y=(100-ih)/2:color=black@0.0" } |
|
else { $filterComplex = "scale=w=100:h=100:force_original_aspect_ratio=decrease,format=rgba,pad=w=100:h=100:x=(100-iw)/2:y=(100-ih)/2:color=black@0.0" } |
|
$ffmpegArgs = @('-i', $file.FullName, '-filter_complex', $filterComplex, '-y', $targetPath, '-loglevel', 'error') |
|
$process = Start-Process -FilePath $ffmpegPath -ArgumentList $ffmpegArgs -NoNewWindow -Wait -PassThru |
|
if ($process.ExitCode -ne 0) { |
|
return "[FAILED] `"$($file.Name)`" (FFmpeg Code: $($process.ExitCode))" |
|
} else { |
|
return "[SUCCESS] `"$($file.Name)`"" |
|
} |
|
} else { |
|
$targetPath = Join-Path -Path $videoOutputDir -ChildPath "$baseName.webm" |
|
$counter = 1; while (Test-Path $targetPath) { $targetPath = Join-Path -Path $videoOutputDir -ChildPath "$baseName-$counter.webm"; $counter++ } |
|
|
|
$maxSize = 65536 |
|
$crfLevels = @(30, 40, 45, 50) |
|
$process = $null |
|
$success = $false |
|
|
|
foreach ($crf in $crfLevels) { |
|
$filterComplex = "[0:2][0:3]alphamerge,scale=w=100:h=100:force_original_aspect_ratio=decrease,pad=w=100:h=100:x=(100-iw)/2:y=(100-ih)/2:color=black@0.0,format=yuva420p,fps=30" |
|
$ffmpegArgs = @($hwaccel_args + @('-i', $file.FullName, '-filter_complex', $filterComplex, '-c:v', $videoEncoder, "-crf", $crf, '-b:v', '0', '-an', '-map_metadata', '-1', '-y', $targetPath, '-loglevel', 'error')) |
|
$process = Start-Process -FilePath $ffmpegPath -ArgumentList $ffmpegArgs -NoNewWindow -Wait -PassThru |
|
|
|
if ($process.ExitCode -eq 0 -and (Test-Path $targetPath) -and (Get-Item $targetPath).Length -lt $maxSize) { |
|
$success = $true; break |
|
} |
|
} |
|
|
|
if (-not $success) { |
|
$worstCrf = $crfLevels[-1] |
|
for ($i = 1; $i -le 8; $i++) { |
|
$setptsMultiplier = [math]::Round(1.0 - ($i * 0.1), 1) |
|
$filterComplex = "[0:2][0:3]alphamerge,scale=w=100:h=100:force_original_aspect_ratio=decrease,pad=w=100:h=100:x=(100-iw)/2:y=(100-ih)/2:color=black@0.0,format=yuva420p,fps=30,setpts=$($setptsMultiplier)*PTS" |
|
$ffmpegArgs = @($hwaccel_args + @('-i', $file.FullName, '-filter_complex', $filterComplex, '-c:v', $videoEncoder, "-crf", $worstCrf, '-b:v', '0', '-an', '-map_metadata', '-1', '-y', $targetPath, '-loglevel', 'error')) |
|
$process = Start-Process -FilePath $ffmpegPath -ArgumentList $ffmpegArgs -NoNewWindow -Wait -PassThru |
|
|
|
if ($process.ExitCode -eq 0 -and (Test-Path $targetPath) -and (Get-Item $targetPath).Length -lt $maxSize) { |
|
$success = $true; break |
|
} |
|
} |
|
} |
|
|
|
if ($process.ExitCode -ne 0) { |
|
return "[FAILED] `"$($file.Name)`" (FFmpeg Code: $($process.ExitCode))" |
|
} elseif (-not (Test-Path $targetPath) -or (Get-Item $targetPath).Length -ge $maxSize) { |
|
return "[FAILED-SIZE] `"$($file.Name)`" - File size is too large after all attempts." |
|
} else { |
|
$patchResult = Set-WebmDuration -FilePath $targetPath -SpoofNumber 555.55 |
|
if (($patchResult -is [bool] -and $patchResult) -or $patchResult -eq "Info tag not found" -or $patchResult -eq "Duration tag not found after Info tag") { |
|
return "[SUCCESS] `"$($file.Name)`"" |
|
} else { |
|
return "[FAILED-META] `"$($file.Name)`" - Reason: $patchResult" |
|
} |
|
} |
|
} |
|
} -ArgumentList @($_, $ffmpegPath, $ffprobePath, $videoOutputDir, $staticOutputDir, $hwaccel_params, $videoEncoder) |
|
} |
|
Write-Host "All $(@($jobs).Count) conversion tasks queued..." |
|
$script:results = $jobs | Wait-Job | Receive-Job |
|
$jobs | Remove-Job |
|
} |
|
|
|
if ($EnableBenchmark) { |
|
$timingResult = Measure-Command { & $conversionLogic } |
|
} else { |
|
& $conversionLogic |
|
} |
|
|
|
$successCount = ($script:results | Where-Object { $_ -like "[SUCCESS]*" }).Count |
|
$failureCount = ($script:results | Where-Object { $_ -like "[FAILED]*" -or $_ -like "[FAILED-META]*" -or $_ -like "[FAILED-SIZE]*" }).Count |
|
Write-Host "`n--- FAILED FILES ---" |
|
$script:results | Where-Object {$_ -like "[FAILED]*" -or $_ -like "[FAILED-META]*" -or $_ -like "[FAILED-SIZE]*"} | ForEach-Object { Write-Warning $_ } |
|
Write-Host "=====================================================================" |
|
if ($EnableBenchmark) { |
|
Write-Host "Total Execution Time: $($timingResult.TotalSeconds) seconds" -ForegroundColor Cyan |
|
Write-Host "=====================================================================" |
|
} |
|
Write-Host "Conversion finished!" |
|
Write-Host "Successful: $successCount / Failed: $failureCount" |
|
Write-Host "=====================================================================" |
|
Read-Host "Press Enter to exit" |