Files
Video-Processing-Scripts/Crop-ClipsWan.ps1
T

211 lines
6.9 KiB
PowerShell

<#
.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 from 1928x1076 or 1926x1076 to 1920x1080, or from 1284x716 to 1280x720 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
# Safe property accessor: returns $null when the property is absent (strict-mode friendly).
$get = { param($obj, $name) if ($obj -and ($obj.PSObject.Properties.Name -contains $name)) { $obj.$name } else { $null } }
$rFrameRate = & $get $videoStream 'r_frame_rate'
$fps = $null
if ($rFrameRate -and $rFrameRate -match '^(\d+)/(\d+)$') {
$num = [double]$Matches[1]
$den = [double]$Matches[2]
if ($den -ne 0) {
$fps = $num / $den
}
}
$videoBitRate = & $get $videoStream 'bit_rate'
$formatBitRate = & $get $info.format 'bit_rate'
$bitRate = if ($videoBitRate) { $videoBitRate } else { $formatBitRate }
[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 = $videoBitRate
}
}
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' }
$isValidResolution = ($inInfo.Width -eq 1928 -and $inInfo.Height -eq 1076) -or
($inInfo.Width -eq 1926 -and $inInfo.Height -eq 1076) -or
($inInfo.Width -eq 1284 -and $inInfo.Height -eq 716)
if (-not $isValidResolution) {
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
# Determine crop parameters based on input resolution
if ($inInfo.Width -eq 1284 -and $inInfo.Height -eq 716) {
# 1284x716: expand to 720 height, then crop to 1280x720
$videoFilter = 'scale=-2:720,crop=1280:720:(in_w-1280)/2:(in_h-720)/2'
}
else {
# 1928x1076 or 1926x1076: expand to 1080 height, then crop to 1920x1080
$videoFilter = 'scale=-2:1080,crop=1920:1080:(in_w-1920)/2:(in_h-1080)/2'
}
$ffArgs = @(
'-y',
'-i', $inputPath,
'-vf', $videoFilter
)
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"