Compare commits
2
Commits
9d8bf8bad3
...
b823259ab1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b823259ab1 | ||
|
|
274dac5786 |
No files matched your search
+55
-16
@@ -7,6 +7,7 @@
|
|||||||
Path to the output video file. Can be absolute or relative to current directory. .mp4 extension is added if not provided.
|
Path to the output video file. Can be absolute or relative to current directory. .mp4 extension is added if not provided.
|
||||||
If omitted, defaults to the source filename with the -Effect value (lowercased) appended after a dash,
|
If omitted, defaults to the source filename with the -Effect value (lowercased) appended after a dash,
|
||||||
saved alongside the source (e.g. video.mp4 + -Effect black -> video-black.mp4).
|
saved alongside the source (e.g. video.mp4 + -Effect black -> video-black.mp4).
|
||||||
|
When -Vertical is used, '-vertical' is appended after the effect (e.g. video-black-vertical.mp4).
|
||||||
.PARAMETER Effect
|
.PARAMETER Effect
|
||||||
The effect to apply to the side bars. Possible values are:
|
The effect to apply to the side bars. Possible values are:
|
||||||
black: The side bars will be solid black (#000000).
|
black: The side bars will be solid black (#000000).
|
||||||
@@ -18,8 +19,14 @@
|
|||||||
standard: Creates softly blurred side bars generated from the video itself, with a subtle zoom to fill the 16:9 frame.
|
standard: Creates softly blurred side bars generated from the video itself, with a subtle zoom to fill the 16:9 frame.
|
||||||
dark: Produces blurred side bars with reduced brightness, creating a darker, more contrasted background that enhances focus on the main video.
|
dark: Produces blurred side bars with reduced brightness, creating a darker, more contrasted background that enhances focus on the main video.
|
||||||
gaussian: Generates smooth, high‑quality Gaussian‑blurred side bars for a clean, professional broadcast-style background.
|
gaussian: Generates smooth, high‑quality Gaussian‑blurred side bars for a clean, professional broadcast-style background.
|
||||||
|
.PARAMETER Vertical
|
||||||
|
Only valid when the source video has a square (1:1) resolution. When present, the output is a 9:16 portrait
|
||||||
|
video with the bars on the top and bottom instead of the default 16:9 landscape output with bars on the sides.
|
||||||
|
Named -Size presets are interpreted as portrait sizes (e.g. 1080p -> 1080x1920, 4K -> 2160x3840).
|
||||||
|
Throws when the source video is not square.
|
||||||
.PARAMETER Size
|
.PARAMETER Size
|
||||||
Size of the output video. Uses the input video's closest size that will fit the 16:9 aspect ratio if not specified.
|
Size of the output video. Uses the input video's closest size that will fit the 16:9 aspect ratio if not specified
|
||||||
|
(9:16 when -Vertical is used).
|
||||||
Possible values: 480p, 720p, 1080p, HD, 1440p, 2K, 2160p, 4K, or custom size in format "width:height".
|
Possible values: 480p, 720p, 1080p, HD, 1440p, 2K, 2160p, 4K, or custom size in format "width:height".
|
||||||
.PARAMETER FPS
|
.PARAMETER FPS
|
||||||
Frames per second for the output video. Uses the input video's FPS if not specified.
|
Frames per second for the output video. Uses the input video's FPS if not specified.
|
||||||
@@ -33,6 +40,7 @@
|
|||||||
Creates a pillar-boxed video file from a video file. Will add black bars to the sides of the video to match 16:9 aspect ratio.
|
Creates a pillar-boxed video file from a video file. Will add black bars to the sides of the video to match 16:9 aspect ratio.
|
||||||
The black bars will be verticals on each side of the output video if the source video is more portrait-like (e.g. 9:16).
|
The black bars will be verticals on each side of the output video if the source video is more portrait-like (e.g. 9:16).
|
||||||
Otherwise, the black bars will be horizontal on the top and bottom of the output video.
|
Otherwise, the black bars will be horizontal on the top and bottom of the output video.
|
||||||
|
With -Vertical (square sources only), the output frame is 9:16 instead and the bars are horizontal on the top and bottom.
|
||||||
#>
|
#>
|
||||||
[CmdletBinding(SupportsShouldProcess)]
|
[CmdletBinding(SupportsShouldProcess)]
|
||||||
param(
|
param(
|
||||||
@@ -45,6 +53,9 @@ param(
|
|||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
[string]$Effect,
|
[string]$Effect,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[switch]$Vertical,
|
||||||
|
|
||||||
[Parameter(Mandatory = $false)]
|
[Parameter(Mandatory = $false)]
|
||||||
[string]$Size = $null,
|
[string]$Size = $null,
|
||||||
|
|
||||||
@@ -136,7 +147,12 @@ function Resolve-Size {
|
|||||||
[int]$SourceWidth,
|
[int]$SourceWidth,
|
||||||
|
|
||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
[int]$SourceHeight
|
[int]$SourceHeight,
|
||||||
|
|
||||||
|
# When set, the output frame is 9:16 (portrait) instead of 16:9: named presets
|
||||||
|
# are transposed (1080p -> 1080x1920) and the computed frame is portrait.
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[switch]$Vertical
|
||||||
)
|
)
|
||||||
|
|
||||||
# Standard 16:9 sizes (largest first only matters for closest-fit lookup)
|
# Standard 16:9 sizes (largest first only matters for closest-fit lookup)
|
||||||
@@ -154,6 +170,9 @@ function Resolve-Size {
|
|||||||
if ($Value) {
|
if ($Value) {
|
||||||
$named = $standards | Where-Object { $_.Name -ieq $Value } | Select-Object -First 1
|
$named = $standards | Where-Object { $_.Name -ieq $Value } | Select-Object -First 1
|
||||||
if ($named) {
|
if ($named) {
|
||||||
|
if ($Vertical) {
|
||||||
|
return [pscustomobject]@{ Width = $named.Height; Height = $named.Width }
|
||||||
|
}
|
||||||
return [pscustomobject]@{ Width = $named.Width; Height = $named.Height }
|
return [pscustomobject]@{ Width = $named.Width; Height = $named.Height }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,29 +183,41 @@ function Resolve-Size {
|
|||||||
throw "Invalid -Size value: $Value. Use a named preset (480p, 720p, 1080p, HD, 1440p, 2K, 2160p, 4K) or 'width:height'."
|
throw "Invalid -Size value: $Value. Use a named preset (480p, 720p, 1080p, HD, 1440p, 2K, 2160p, 4K) or 'width:height'."
|
||||||
}
|
}
|
||||||
|
|
||||||
# Pick the smallest standard 16:9 preset whose longer side (width) is at least
|
# Pick the smallest standard 16:9 preset that holds the source's bounding box
|
||||||
# the source's longer side. The foreground is scaled down with
|
# once that box is laid out in the frame's orientation: the preset's long side
|
||||||
# 'force_original_aspect_ratio=decrease', so the source is allowed to shrink
|
# (width) must be at least the source's long side AND the preset's short side
|
||||||
# to fit; we only need the preset to be "big enough" relative to the source's
|
# (height) must be at least the source's short side. Checking both sides matters
|
||||||
# largest dimension. This produces the documented behavior:
|
# for square-ish sources: a 1080x1080 source fits 1920x1080 at full resolution,
|
||||||
|
# while 1280x720 would force it down to 720x720.
|
||||||
|
# This produces the documented behavior:
|
||||||
# 1080x1920 portrait -> 1920x1080 (vertical bars on either side)
|
# 1080x1920 portrait -> 1920x1080 (vertical bars on either side)
|
||||||
# 720x1280 portrait -> 1280x720
|
# 720x1280 portrait -> 1280x720
|
||||||
|
# 1080x1080 square -> 1920x1080 (vertical bars, source kept at 1080x1080)
|
||||||
# 2560x1080 landscape -> 2560x1440 (horizontal bars top/bottom)
|
# 2560x1080 landscape -> 2560x1440 (horizontal bars top/bottom)
|
||||||
|
# With -Vertical the same preset is used transposed (e.g. 1080x1080 -> 1080x1920).
|
||||||
$longSide = [math]::Max($SourceWidth, $SourceHeight)
|
$longSide = [math]::Max($SourceWidth, $SourceHeight)
|
||||||
|
$shortSide = [math]::Min($SourceWidth, $SourceHeight)
|
||||||
|
|
||||||
$unique = $standards | Sort-Object { $_.Width } -Unique
|
$unique = $standards | Sort-Object { $_.Width } -Unique
|
||||||
foreach ($s in $unique) {
|
foreach ($s in $unique) {
|
||||||
if ($s.Width -ge $longSide) {
|
if ($s.Width -ge $longSide -and $s.Height -ge $shortSide) {
|
||||||
|
if ($Vertical) {
|
||||||
|
return [pscustomobject]@{ Width = $s.Height; Height = $s.Width }
|
||||||
|
}
|
||||||
return [pscustomobject]@{ Width = $s.Width; Height = $s.Height }
|
return [pscustomobject]@{ Width = $s.Width; Height = $s.Height }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Source larger than any standard preset: build a 16:9 frame whose width
|
# Source larger than any standard preset: build a 16:9 (or 9:16) frame that
|
||||||
# matches the source's longer side (rounded to even).
|
# holds the source's bounding box, rounded to even dimensions.
|
||||||
$w = $longSide + ($longSide % 2)
|
$long = [math]::Max($longSide, [int][math]::Ceiling($shortSide * 16.0 / 9.0))
|
||||||
$h = [int][math]::Ceiling($w * 9.0 / 16.0)
|
$short = [math]::Max($shortSide, [int][math]::Ceiling($long * 9.0 / 16.0))
|
||||||
if ($h % 2) { $h++ }
|
if ($long % 2) { $long++ }
|
||||||
return [pscustomobject]@{ Width = $w; Height = $h }
|
if ($short % 2) { $short++ }
|
||||||
|
if ($Vertical) {
|
||||||
|
return [pscustomobject]@{ Width = $short; Height = $long }
|
||||||
|
}
|
||||||
|
return [pscustomobject]@{ Width = $long; Height = $short }
|
||||||
}
|
}
|
||||||
|
|
||||||
# Validate -Effect first so it can be used in the default -Target name.
|
# Validate -Effect first so it can be used in the default -Target name.
|
||||||
@@ -231,11 +262,13 @@ $sourceFull = (Resolve-Path -LiteralPath $Source).Path
|
|||||||
# (e.g. C:\clips\video.mp4 + -Effect Black -> C:\clips\video-black.mp4).
|
# (e.g. C:\clips\video.mp4 + -Effect Black -> C:\clips\video-black.mp4).
|
||||||
# For 'color:RRGGBB', the colon is replaced with a dash to keep the filename valid
|
# For 'color:RRGGBB', the colon is replaced with a dash to keep the filename valid
|
||||||
# (e.g. -Effect color:FFFF00 -> video-color-ffff00.mp4).
|
# (e.g. -Effect color:FFFF00 -> video-color-ffff00.mp4).
|
||||||
|
# With -Vertical, '-vertical' is appended last (e.g. video-black-vertical.mp4).
|
||||||
if ([string]::IsNullOrWhiteSpace($Target)) {
|
if ([string]::IsNullOrWhiteSpace($Target)) {
|
||||||
$srcDir = [System.IO.Path]::GetDirectoryName($sourceFull)
|
$srcDir = [System.IO.Path]::GetDirectoryName($sourceFull)
|
||||||
$srcBase = [System.IO.Path]::GetFileNameWithoutExtension($sourceFull)
|
$srcBase = [System.IO.Path]::GetFileNameWithoutExtension($sourceFull)
|
||||||
$effectForName = $effectLower -replace ':', '-'
|
$effectForName = $effectLower -replace ':', '-'
|
||||||
$Target = Join-Path $srcDir "$srcBase-$effectForName.mp4"
|
$verticalForName = if ($Vertical) { '-vertical' } else { '' }
|
||||||
|
$Target = Join-Path $srcDir "$srcBase-$effectForName$verticalForName.mp4"
|
||||||
}
|
}
|
||||||
elseif (-not [System.IO.Path]::GetExtension($Target)) {
|
elseif (-not [System.IO.Path]::GetExtension($Target)) {
|
||||||
$Target = "$Target.mp4"
|
$Target = "$Target.mp4"
|
||||||
@@ -271,7 +304,13 @@ $inQuality = Format-Quality -BitRate $inInfo.BitRate
|
|||||||
$inAudioText = if ($inInfo.HasAudio) { 'yes' } else { 'no' }
|
$inAudioText = if ($inInfo.HasAudio) { 'yes' } else { 'no' }
|
||||||
Write-Host "Input : Filename=$($inInfo.FileName) Resolution=$inRes FPS=$inFpsText Quality=$inQuality Audio=$inAudioText"
|
Write-Host "Input : Filename=$($inInfo.FileName) Resolution=$inRes FPS=$inFpsText Quality=$inQuality Audio=$inAudioText"
|
||||||
|
|
||||||
$outSize = Resolve-Size -Value $Size -SourceWidth $inInfo.Width -SourceHeight $inInfo.Height
|
# -Vertical produces a 9:16 output frame (bars on top and bottom) and is only
|
||||||
|
# meaningful for square sources, where the 16:9 / 9:16 choice is otherwise arbitrary.
|
||||||
|
if ($Vertical -and $inInfo.Width -ne $inInfo.Height) {
|
||||||
|
throw "-Vertical requires a square (1:1) source video, but '$($inInfo.FileName)' is $inRes."
|
||||||
|
}
|
||||||
|
|
||||||
|
$outSize = Resolve-Size -Value $Size -SourceWidth $inInfo.Width -SourceHeight $inInfo.Height -Vertical:$Vertical
|
||||||
$outWidth = $outSize.Width
|
$outWidth = $outSize.Width
|
||||||
$outHeight = $outSize.Height
|
$outHeight = $outSize.Height
|
||||||
|
|
||||||
|
|||||||
+55
-67
@@ -6,7 +6,9 @@
|
|||||||
.PARAMETER Target
|
.PARAMETER Target
|
||||||
Target directory for cropped video files.
|
Target directory for cropped video files.
|
||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
Crops video clips using ffmpeg.
|
Crops video clips to standard resolutions using ffmpeg. Handles the non-standard resolutions
|
||||||
|
produced by LTX-2, Kling and Wan (see the $cropProfiles table), plus any square (1:1) source,
|
||||||
|
which is scaled to 1080x1080. Videos with any other resolution are skipped.
|
||||||
Supports -WhatIf and -Confirm for safe execution.
|
Supports -WhatIf and -Confirm for safe execution.
|
||||||
#>
|
#>
|
||||||
[CmdletBinding(SupportsShouldProcess)]
|
[CmdletBinding(SupportsShouldProcess)]
|
||||||
@@ -83,6 +85,50 @@ function Format-Quality {
|
|||||||
return "${kbps}kbps"
|
return "${kbps}kbps"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Known non-standard generator resolutions and the ffmpeg filter that brings each one
|
||||||
|
# back to a standard size. Resolutions are unique across all generators, so a single
|
||||||
|
# lookup is unambiguous. Any resolution not listed here is skipped, except square
|
||||||
|
# sources, which are handled by a generic rule in the loop below.
|
||||||
|
$cropProfiles = @(
|
||||||
|
# LTX-2 landscape
|
||||||
|
@{ Width = 1920; Height = 1088; Filter = 'crop=1920:1080:0:4' }
|
||||||
|
@{ Width = 1280; Height = 704; Filter = 'scale=-2:720,crop=1280:720:(in_w-1280)/2:(in_h-720)/2' }
|
||||||
|
@{ Width = 1600; Height = 896; Filter = 'scale=-2:900,crop=1600:900:(in_w-1600)/2:(in_h-900)/2' }
|
||||||
|
@{ Width = 832; Height = 448; Filter = 'scale=-2:480,crop=854:480:(in_w-854)/2:(in_h-480)/2' }
|
||||||
|
@{ Width = 640; Height = 384; Filter = 'scale=684:-2,crop=684:384:(in_w-684)/2:(in_h-384)/2' }
|
||||||
|
@{ Width = 576; Height = 320; Filter = 'crop=570:320:3:0' }
|
||||||
|
@{ Width = 3744; Height = 2112; Filter = 'scale=3840:-2,crop=3840:2160:0:(in_h-2160)/2' }
|
||||||
|
@{ Width = 2496; Height = 1408; Filter = 'scale=2560:-2,crop=2560:1440:0:(in_h-1440)/2' }
|
||||||
|
@{ Width = 2160; Height = 1216; Filter = 'scale=1920:-2,crop=1920:1080:0:(in_h-1080)/2' }
|
||||||
|
@{ Width = 1872; Height = 1056; Filter = 'scale=1920:-2,crop=1920:1080:0:(in_h-1080)/2' }
|
||||||
|
@{ Width = 1248; Height = 704; Filter = 'scale=1280:-2,crop=1280:720:0:(in_h-720)/2' }
|
||||||
|
|
||||||
|
# LTX-2 portrait
|
||||||
|
@{ Width = 1088; Height = 1920; Filter = 'crop=1080:1920:4:0' }
|
||||||
|
@{ Width = 704; Height = 1280; Filter = 'scale=720:-2,crop=720:1280:(in_w-720)/2:(in_h-1280)/2' }
|
||||||
|
@{ Width = 896; Height = 1600; Filter = 'scale=900:-2,crop=900:1600:(in_w-900)/2:(in_h-1600)/2' }
|
||||||
|
@{ Width = 448; Height = 832; Filter = 'scale=480:-2,crop=480:854:(in_w-480)/2:(in_h-854)/2' }
|
||||||
|
@{ Width = 384; Height = 640; Filter = 'scale=-2:684,crop=384:684:(in_w-384)/2:(in_h-684)/2' }
|
||||||
|
@{ Width = 320; Height = 576; Filter = 'crop=320:570:0:3' }
|
||||||
|
@{ Width = 2112; Height = 3744; Filter = 'scale=-2:3840,crop=2160:3840:(in_w-2160)/2:0' }
|
||||||
|
@{ Width = 1408; Height = 2496; Filter = 'scale=-2:2560,crop=1440:2560:(in_w-1440)/2:0' }
|
||||||
|
@{ Width = 1216; Height = 2160; Filter = 'scale=-2:1920,crop=1080:1920:(in_w-1080)/2:0' }
|
||||||
|
@{ Width = 1056; Height = 1872; Filter = 'scale=-2:1920,crop=1080:1920:(in_w-1080)/2:0' }
|
||||||
|
@{ Width = 704; Height = 1248; Filter = 'scale=-2:1280,crop=720:1280:(in_w-720)/2:0' }
|
||||||
|
|
||||||
|
# Kling
|
||||||
|
@{ Width = 1920; Height = 1072; Filter = 'scale=-2:1080,crop=1920:1080:(in_w-1920)/2:(in_h-1080)/2' }
|
||||||
|
@{ Width = 1928; Height = 1072; Filter = 'scale=-2:1080,crop=1920:1080:(in_w-1920)/2:(in_h-1080)/2' }
|
||||||
|
@{ Width = 1920; Height = 1076; Filter = 'scale=-2:1080,crop=1920:1080:(in_w-1920)/2:(in_h-1080)/2' }
|
||||||
|
# 1916x1080 is already 1080 tall: pad the missing 4 columns instead of rescaling.
|
||||||
|
@{ Width = 1916; Height = 1080; Filter = 'pad=1920:1082:2:1:black,crop=1920:1080:0:1' }
|
||||||
|
|
||||||
|
# Wan
|
||||||
|
@{ Width = 1928; Height = 1076; Filter = 'scale=-2:1080,crop=1920:1080:(in_w-1920)/2:(in_h-1080)/2' }
|
||||||
|
@{ Width = 1926; Height = 1076; Filter = 'scale=-2:1080,crop=1920:1080:(in_w-1920)/2:(in_h-1080)/2' }
|
||||||
|
@{ Width = 1284; Height = 716; Filter = 'scale=-2:720,crop=1280:720:(in_w-1280)/2:(in_h-720)/2' }
|
||||||
|
)
|
||||||
|
|
||||||
$sourceFull = (Resolve-Path -LiteralPath $Source).Path
|
$sourceFull = (Resolve-Path -LiteralPath $Source).Path
|
||||||
$targetFull = (Resolve-Path -LiteralPath $Target).Path
|
$targetFull = (Resolve-Path -LiteralPath $Target).Path
|
||||||
|
|
||||||
@@ -112,76 +158,18 @@ foreach ($video in $videos) {
|
|||||||
$inQuality = Format-Quality -BitRate $inInfo.BitRate
|
$inQuality = Format-Quality -BitRate $inInfo.BitRate
|
||||||
$inAudioText = if ($inInfo.HasAudio) { 'yes' } else { 'no' }
|
$inAudioText = if ($inInfo.HasAudio) { 'yes' } else { 'no' }
|
||||||
|
|
||||||
$vf = $null
|
$cropProfile = $cropProfiles | Where-Object { $_.Width -eq $inInfo.Width -and $_.Height -eq $inInfo.Height } | Select-Object -First 1
|
||||||
if ($inInfo.Width -eq 1920 -and $inInfo.Height -eq 1088) {
|
|
||||||
$vf = 'crop=1920:1080:0:4'
|
if ($cropProfile) {
|
||||||
}
|
$vf = $cropProfile.Filter
|
||||||
elseif ($inInfo.Width -eq 1280 -and $inInfo.Height -eq 704) {
|
|
||||||
$vf = 'scale=-2:720,crop=1280:720:(in_w-1280)/2:(in_h-720)/2'
|
|
||||||
}
|
|
||||||
elseif ($inInfo.Width -eq 1088 -and $inInfo.Height -eq 1920) {
|
|
||||||
$vf = 'crop=1080:1920:4:0'
|
|
||||||
}
|
|
||||||
elseif ($inInfo.Width -eq 704 -and $inInfo.Height -eq 1280) {
|
|
||||||
$vf = 'scale=720:-2,crop=720:1280:(in_w-720)/2:(in_h-1280)/2'
|
|
||||||
}
|
|
||||||
elseif ($inInfo.Width -eq 832 -and $inInfo.Height -eq 448) {
|
|
||||||
$vf = 'scale=-2:480,crop=854:480:(in_w-854)/2:(in_h-480)/2'
|
|
||||||
}
|
|
||||||
elseif ($inInfo.Width -eq 448 -and $inInfo.Height -eq 832) {
|
|
||||||
$vf = 'scale=480:-2,crop=480:854:(in_w-480)/2:(in_h-854)/2'
|
|
||||||
}
|
|
||||||
elseif ($inInfo.Width -eq 1600 -and $inInfo.Height -eq 896) {
|
|
||||||
$vf = 'scale=-2:900,crop=1600:900:(in_w-1600)/2:(in_h-900)/2'
|
|
||||||
}
|
|
||||||
elseif ($inInfo.Width -eq 640 -and $inInfo.Height -eq 384) {
|
|
||||||
$vf = 'scale=684:-2,crop=684:384:(in_w-684)/2:(in_h-384)/2'
|
|
||||||
}
|
|
||||||
elseif ($inInfo.Width -eq 576 -and $inInfo.Height -eq 320) {
|
|
||||||
$vf = 'crop=570:320:3:0'
|
|
||||||
}
|
|
||||||
elseif ($inInfo.Width -eq 896 -and $inInfo.Height -eq 1600) {
|
|
||||||
$vf = 'scale=900:-2,crop=900:1600:(in_w-900)/2:(in_h-1600)/2'
|
|
||||||
}
|
|
||||||
elseif ($inInfo.Width -eq 384 -and $inInfo.Height -eq 640) {
|
|
||||||
$vf = 'scale=-2:684,crop=384:684:(in_w-384)/2:(in_h-684)/2'
|
|
||||||
}
|
|
||||||
elseif ($inInfo.Width -eq 320 -and $inInfo.Height -eq 576) {
|
|
||||||
$vf = 'crop=320:570:0:3'
|
|
||||||
}
|
|
||||||
elseif ($inInfo.Width -eq 1248 -and $inInfo.Height -eq 704) {
|
|
||||||
$vf = 'scale=1280:-2,crop=1280:720:0:(in_h-720)/2'
|
|
||||||
}
|
|
||||||
elseif ($inInfo.Width -eq 1872 -and $inInfo.Height -eq 1056) {
|
|
||||||
$vf = 'scale=1920:-2,crop=1920:1080:0:(in_h-1080)/2'
|
|
||||||
}
|
|
||||||
elseif ($inInfo.Width -eq 2160 -and $inInfo.Height -eq 1216) {
|
|
||||||
$vf = 'scale=1920:-2,crop=1920:1080:0:(in_h-1080)/2'
|
|
||||||
}
|
|
||||||
elseif ($inInfo.Width -eq 2496 -and $inInfo.Height -eq 1408) {
|
|
||||||
$vf = 'scale=2560:-2,crop=2560:1440:0:(in_h-1440)/2'
|
|
||||||
}
|
|
||||||
elseif ($inInfo.Width -eq 3744 -and $inInfo.Height -eq 2112) {
|
|
||||||
$vf = 'scale=3840:-2,crop=3840:2160:0:(in_h-2160)/2'
|
|
||||||
}
|
|
||||||
elseif ($inInfo.Width -eq 704 -and $inInfo.Height -eq 1248) {
|
|
||||||
$vf = 'scale=-2:1280,crop=720:1280:(in_w-720)/2:0'
|
|
||||||
}
|
|
||||||
elseif ($inInfo.Width -eq 1056 -and $inInfo.Height -eq 1872) {
|
|
||||||
$vf = 'scale=-2:1920,crop=1080:1920:(in_w-1080)/2:0'
|
|
||||||
}
|
|
||||||
elseif ($inInfo.Width -eq 1216 -and $inInfo.Height -eq 2160) {
|
|
||||||
$vf = 'scale=-2:1920,crop=1080:1920:(in_w-1080)/2:0'
|
|
||||||
}
|
|
||||||
elseif ($inInfo.Width -eq 1408 -and $inInfo.Height -eq 2496) {
|
|
||||||
$vf = 'scale=-2:2560,crop=1440:2560:(in_w-1440)/2:0'
|
|
||||||
}
|
|
||||||
elseif ($inInfo.Width -eq 2112 -and $inInfo.Height -eq 3744) {
|
|
||||||
$vf = 'scale=-2:3840,crop=2160:3840:(in_w-2160)/2:0'
|
|
||||||
}
|
}
|
||||||
elseif ($inInfo.Width -eq $inInfo.Height -and $inInfo.Width -ne 1080) {
|
elseif ($inInfo.Width -eq $inInfo.Height -and $inInfo.Width -ne 1080) {
|
||||||
|
# Any square (1:1) source other than 1080x1080 is scaled to 1080x1080; no crop needed.
|
||||||
$vf = 'scale=1080:1080'
|
$vf = 'scale=1080:1080'
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
$vf = $null
|
||||||
|
}
|
||||||
|
|
||||||
if (-not $vf) {
|
if (-not $vf) {
|
||||||
Write-Host "Skipping: $($inInfo.FileName) (resolution $inRes)"
|
Write-Host "Skipping: $($inInfo.FileName) (resolution $inRes)"
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
@echo off
|
|
||||||
pwsh.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0Crop-ClipsKling.ps1" %*
|
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
<#
|
|
||||||
.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
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
$is1072 = ($inInfo.Height -eq 1072 -and ($inInfo.Width -eq 1920 -or $inInfo.Width -eq 1928))
|
|
||||||
$is1916x1080 = ($inInfo.Width -eq 1916 -and $inInfo.Height -eq 1080)
|
|
||||||
$is1920x1076 = ($inInfo.Width -eq 1920 -and $inInfo.Height -eq 1076)
|
|
||||||
|
|
||||||
if (-not $is1072 -and -not $is1916x1080 -and -not $is1920x1076) {
|
|
||||||
Write-Host "Skipping: $($inInfo.FileName) (resolution $inRes)"
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
if ($is1916x1080) {
|
|
||||||
$vfFilter = 'pad=1920:1082:2:1:black,crop=1920:1080:0:1'
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$vfFilter = 'scale=-2:1080,crop=1920:1080:(in_w-1920)/2:(in_h-1080)/2'
|
|
||||||
}
|
|
||||||
|
|
||||||
$ffArgs = @(
|
|
||||||
'-y',
|
|
||||||
'-i', $inputPath,
|
|
||||||
'-vf', $vfFilter
|
|
||||||
)
|
|
||||||
|
|
||||||
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"
|
|
||||||
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
@echo off
|
|
||||||
pwsh.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0Crop-ClipsWan.ps1" %*
|
|
||||||
@@ -1,210 +0,0 @@
|
|||||||
<#
|
|
||||||
.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"
|
|
||||||
|
|
||||||
@@ -27,9 +27,7 @@ To install rife-ncnn-vulkan on Windows, download it from [rife-ncnn-vulkan](http
|
|||||||
| Compare-Videos.ps1 | Combine 2 to 4 video files into a single 1920x1080 side-by-side or grid comparison video |
|
| Compare-Videos.ps1 | Combine 2 to 4 video files into a single 1920x1080 side-by-side or grid comparison video |
|
||||||
| Create-FullSubtitle.ps1 | Creates a full subtitle file from a video file and two SRT subtitle files by combining them with adjusted timing and sequential numbering |
|
| Create-FullSubtitle.ps1 | Creates a full subtitle file from a video file and two SRT subtitle files by combining them with adjusted timing and sequential numbering |
|
||||||
| Create-ShortClip.ps1 | Create short clips from videos with cropping and overlay options |
|
| Create-ShortClip.ps1 | Create short clips from videos with cropping and overlay options |
|
||||||
| Crop-Clips.ps1 | Batch crop videos generated by LTX-2 from 1920x1088 or 1280x704 resolution |
|
| Crop-Clips.ps1 | Batch crop videos generated by LTX-2, Kling and Wan from their non-standard resolutions to standard ones (also scales any square video to 1080x1080) |
|
||||||
| Crop-ClipsKling.ps1 | Batch crop videos generated by Kling from 1920x1072 or 1928x1072 resolution |
|
|
||||||
| Crop-ClipsWan.ps1 | Batch crop videos generated by Wan from 1928x1076 resolution |
|
|
||||||
| Get-Frames.ps1 | Extract a range of frames to PNG images |
|
| Get-Frames.ps1 | Extract a range of frames to PNG images |
|
||||||
| Get-FramesCount.ps1 | Get total frame count of a video |
|
| Get-FramesCount.ps1 | Get total frame count of a video |
|
||||||
| Get-LastFrame.ps1 | Extract a specific frame or last frame to PNG |
|
| Get-LastFrame.ps1 | Extract a specific frame or last frame to PNG |
|
||||||
|
|||||||
Reference in new issue
Block a user