Inital push

This commit is contained in:
2026-05-19 14:55:09 -04:00
commit 7f2f0754b3
29 changed files with 3190 additions and 0 deletions
+193
View File
@@ -0,0 +1,193 @@
<#
.SYNOPSIS
Crops video clips using ffmpeg.
.PARAMETER Source
Source directory containing video files.
.PARAMETER Target
Target directory for cropped video files.
.DESCRIPTION
Crops video clips using ffmpeg.
Supports -WhatIf and -Confirm for safe execution.
#>
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory = $false)]
[string]$Source = '.',
[Parameter(Mandatory = $false)]
[string]$Target = '.'
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
function Get-VideoInfo {
param(
[Parameter(Mandatory = $true)]
[string]$Path
)
$json = & ffprobe -v error -print_format json -show_streams -show_format -- "$Path"
if ($LASTEXITCODE -ne 0 -or -not $json) {
throw "ffprobe failed for: $Path"
}
$info = $json | ConvertFrom-Json
$videoStream = $info.streams | Where-Object { $_.codec_type -eq 'video' } | Select-Object -First 1
if (-not $videoStream) {
throw "No video stream found in: $Path"
}
$audioStream = $info.streams | Where-Object { $_.codec_type -eq 'audio' } | Select-Object -First 1
$fps = $null
if ($videoStream.r_frame_rate -and $videoStream.r_frame_rate -match '^(\d+)/(\d+)$') {
$num = [double]$Matches[1]
$den = [double]$Matches[2]
if ($den -ne 0) {
$fps = $num / $den
}
}
$bitRate = $videoStream.bit_rate
if (-not $bitRate) {
$bitRate = $info.format.bit_rate
}
[pscustomobject]@{
Path = $Path
FileName = [System.IO.Path]::GetFileName($Path)
Width = [int]$videoStream.width
Height = [int]$videoStream.height
Fps = $fps
BitRate = $bitRate
HasAudio = [bool]$audioStream
VideoBitRate = $videoStream.bit_rate
}
}
function Format-Quality {
param(
[Parameter(Mandatory = $false)]
$BitRate
)
if (-not $BitRate -or $BitRate -notmatch '^\d+$') {
return 'unknown'
}
$kbps = [math]::Round(([double]$BitRate) / 1000.0)
return "${kbps}kbps"
}
$sourceFull = (Resolve-Path -LiteralPath $Source).Path
$targetFull = (Resolve-Path -LiteralPath $Target).Path
$BackupRequired = ($sourceFull.TrimEnd('\') -ieq $targetFull.TrimEnd('\'))
$processedCount = 0
$videos = Get-ChildItem -LiteralPath $sourceFull -Filter '*.mp4' -File
foreach ($video in $videos) {
if ($video.Name -like '*.bak.mp4' -or $video.Name -like '*.skip.mp4') {
continue
}
$inputPath = $video.FullName
$inInfo = $null
try {
$inInfo = Get-VideoInfo -Path $inputPath
}
catch {
Write-Error $_
throw
}
$inRes = "{0}x{1}" -f $inInfo.Width, $inInfo.Height
$inFpsText = if ($inInfo.Fps) { "{0:0.###}" -f $inInfo.Fps } else { 'unknown' }
$inQuality = Format-Quality -BitRate $inInfo.BitRate
$inAudioText = if ($inInfo.HasAudio) { 'yes' } else { 'no' }
if ($inInfo.Height -ne 1072 -or ($inInfo.Width -ne 1920 -and $inInfo.Width -ne 1928)) {
Write-Host "Skipping: $($inInfo.FileName) (resolution $inRes)"
continue
}
Write-Host "Input : Filename=$($inInfo.FileName) Resolution=$inRes FPS=$inFpsText Quality=$inQuality Audio=$inAudioText"
if ($BackupRequired) {
$backupPath = Join-Path $video.DirectoryName ($video.BaseName + '.bak' + $video.Extension)
if ($PSCmdlet.ShouldProcess($backupPath, 'Create backup of original video')) {
Copy-Item -LiteralPath $inputPath -Destination $backupPath -Force
}
}
$outCropPath = Join-Path $targetFull ($video.BaseName + '.crop.mp4')
$outFinalPath = Join-Path $targetFull $video.Name
$ffArgs = @(
'-y',
'-i', $inputPath,
'-vf', 'scale=-2:1080,crop=1920:1080:(in_w-1920)/2:(in_h-1080)/2'
)
if ($inInfo.HasAudio) {
$ffArgs += @('-c:a', 'copy')
}
else {
$ffArgs += @('-an')
}
if ($inInfo.VideoBitRate -and $inInfo.VideoBitRate -match '^\d+$') {
$ffArgs += @('-b:v', $inInfo.VideoBitRate)
}
else {
$ffArgs += @('-crf', '8', '-preset', 'slow')
}
$ffArgs += @($outCropPath)
if ($PSCmdlet.ShouldProcess($outCropPath, 'Crop video with ffmpeg')) {
$ffmpegOutput = & ffmpeg @ffArgs 2>&1
$ffmpegExit = $LASTEXITCODE
if ($ffmpegExit -ne 0) {
if (Test-Path -LiteralPath $outCropPath) {
Remove-Item -LiteralPath $outCropPath -Force -ErrorAction SilentlyContinue
}
$ffmpegOutput | ForEach-Object { Write-Error $_ }
Write-Error "ffmpeg failed while cropping: $($inInfo.FileName)"
Write-Host "ffmpeg $($ffArgs -join ' ')"
exit 1
}
if (($BackupRequired) -and ($PSCmdlet.ShouldProcess($inputPath, 'Remove original video after backup'))) {
Remove-Item -LiteralPath $inputPath -Force
}
if ((Test-Path -LiteralPath $outFinalPath) -and ($PSCmdlet.ShouldProcess($outFinalPath, 'Remove existing output file'))) {
Remove-Item -LiteralPath $outFinalPath -Force
}
if ($PSCmdlet.ShouldProcess("$outCropPath -> $outFinalPath", 'Move cropped video to final location')) {
Move-Item -LiteralPath $outCropPath -Destination $outFinalPath -Force
}
} else {
$formattedArgs = $ffArgs | ForEach-Object { if ($_ -match '\s') { "`"$_`"" } else { $_ } }
Write-Host "WhatIf: Would crop video with ffmpeg $($formattedArgs -join ' ')"
}
$outInfo = Get-VideoInfo -Path $outFinalPath
$outRes = "{0}x{1}" -f $outInfo.Width, $outInfo.Height
$outFpsText = if ($outInfo.Fps) { "{0:0.###}" -f $outInfo.Fps } else { 'unknown' }
$outQuality = Format-Quality -BitRate $outInfo.BitRate
$outAudioText = if ($outInfo.HasAudio) { 'yes' } else { 'no' }
Write-Host "Output: Filename=$($outInfo.FileName) Resolution=$outRes FPS=$outFpsText Quality=$outQuality Audio=$outAudioText"
$processedCount++
}
Write-Host "Processed (cropped) videos: $processedCount"