Add -Reverse parameter to reencode video backward while keeping audio forward, and add 1284x716 to 1280x720 crop support in Crop-ClipsWan
This commit is contained in:
+41
-10
@@ -33,6 +33,11 @@
|
|||||||
Constant Rate Factor for video encoding (0-51, lower is better quality). When specified, overrides both the video bitrate and the default -crf 8 value. Only used when re-encoding is required.
|
Constant Rate Factor for video encoding (0-51, lower is better quality). When specified, overrides both the video bitrate and the default -crf 8 value. Only used when re-encoding is required.
|
||||||
.PARAMETER Metadata
|
.PARAMETER Metadata
|
||||||
When present, copies available JSON metadata from the source video file to the target video file.
|
When present, copies available JSON metadata from the source video file to the target video file.
|
||||||
|
.PARAMETER Reverse
|
||||||
|
When present, re-encodes the video so it plays backward (from the last selected frame to the first).
|
||||||
|
Honours all other parameters (StartFrame/EndFrame range, Size, FPS, CRF, container choice, etc.).
|
||||||
|
Audio is NOT reversed; it is processed exactly as it would be without -Reverse (trimmed/replaced/copied as applicable) and plays forward alongside the reversed video.
|
||||||
|
Forces a video re-encode, since the reverse filter cannot be applied to a stream-copied video.
|
||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
This script uses ffmpeg to clone (copy) a video file with optional frame range and audio exclusion.
|
This script uses ffmpeg to clone (copy) a video file with optional frame range and audio exclusion.
|
||||||
When Sequence is provided, it extracts a specific frame to PNG and then clones the video accordingly.
|
When Sequence is provided, it extracts a specific frame to PNG and then clones the video accordingly.
|
||||||
@@ -71,7 +76,10 @@ param(
|
|||||||
[int]$CRF = -1,
|
[int]$CRF = -1,
|
||||||
|
|
||||||
[Parameter(Mandatory = $false)]
|
[Parameter(Mandatory = $false)]
|
||||||
[switch]$Metadata
|
[switch]$Metadata,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[switch]$Reverse
|
||||||
)
|
)
|
||||||
|
|
||||||
Set-StrictMode -Version Latest
|
Set-StrictMode -Version Latest
|
||||||
@@ -173,7 +181,10 @@ if (-not [string]::IsNullOrWhiteSpace($Audio)) {
|
|||||||
# rather than stream-copied.
|
# rather than stream-copied.
|
||||||
$ffprobeExtAudioOutput = & ffprobe -v error -select_streams a:0 -show_entries stream=codec_name -of default=noprint_wrappers=1:nokey=1 -- $AudioPath 2>&1
|
$ffprobeExtAudioOutput = & ffprobe -v error -select_streams a:0 -show_entries stream=codec_name -of default=noprint_wrappers=1:nokey=1 -- $AudioPath 2>&1
|
||||||
if ($LASTEXITCODE -eq 0) {
|
if ($LASTEXITCODE -eq 0) {
|
||||||
$externalAudioCodec = ($ffprobeExtAudioOutput | Select-Object -First 1).Trim().ToLowerInvariant()
|
$extAudioLine = $ffprobeExtAudioOutput | Select-Object -First 1
|
||||||
|
if ($extAudioLine) {
|
||||||
|
$externalAudioCodec = ([string]$extAudioLine).Trim().ToLowerInvariant()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,7 +226,8 @@ if ($ffprobeExit -ne 0) {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
$fpsRatio = ($ffprobeFpsOutput | Select-Object -First 1).Trim()
|
$fpsRatioLine = $ffprobeFpsOutput | Select-Object -First 1
|
||||||
|
$fpsRatio = if ($fpsRatioLine) { ([string]$fpsRatioLine).Trim() } else { '' }
|
||||||
|
|
||||||
if (-not $fpsRatio -or $fpsRatio -notmatch '^\d+/\d+$') {
|
if (-not $fpsRatio -or $fpsRatio -notmatch '^\d+/\d+$') {
|
||||||
Write-Error ('Could not determine frame rate for: {0}' -f $SourcePath)
|
Write-Error ('Could not determine frame rate for: {0}' -f $SourcePath)
|
||||||
@@ -235,7 +247,8 @@ if ($ffprobeExit -ne 0) {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
$totalFrames = ($ffprobeFramesOutput | Select-Object -First 1).Trim()
|
$totalFramesLine = $ffprobeFramesOutput | Select-Object -First 1
|
||||||
|
$totalFrames = if ($totalFramesLine) { ([string]$totalFramesLine).Trim() } else { '' }
|
||||||
|
|
||||||
if (-not $totalFrames -or $totalFrames -notmatch '^\d+$') {
|
if (-not $totalFrames -or $totalFrames -notmatch '^\d+$') {
|
||||||
Write-Error ('Could not determine frame count for: {0}' -f $SourcePath)
|
Write-Error ('Could not determine frame count for: {0}' -f $SourcePath)
|
||||||
@@ -250,9 +263,12 @@ $ffprobeExit = $LASTEXITCODE
|
|||||||
|
|
||||||
$videoBitrate = $null
|
$videoBitrate = $null
|
||||||
if ($ffprobeExit -eq 0) {
|
if ($ffprobeExit -eq 0) {
|
||||||
$bitrateValue = ($ffprobeBitrateOutput | Select-Object -First 1).Trim()
|
$bitrateLine = $ffprobeBitrateOutput | Select-Object -First 1
|
||||||
if ($bitrateValue -and $bitrateValue -match '^\d+$') {
|
if ($bitrateLine) {
|
||||||
$videoBitrate = $bitrateValue
|
$bitrateValue = ([string]$bitrateLine).Trim()
|
||||||
|
if ($bitrateValue -and $bitrateValue -match '^\d+$') {
|
||||||
|
$videoBitrate = $bitrateValue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,13 +278,19 @@ if ($ffprobeExit -eq 0) {
|
|||||||
$ffprobeVideoCodecOutput = & ffprobe -v error -select_streams v:0 -show_entries stream=codec_name -of default=noprint_wrappers=1:nokey=1 -- $SourcePath 2>&1
|
$ffprobeVideoCodecOutput = & ffprobe -v error -select_streams v:0 -show_entries stream=codec_name -of default=noprint_wrappers=1:nokey=1 -- $SourcePath 2>&1
|
||||||
$sourceVideoCodec = ''
|
$sourceVideoCodec = ''
|
||||||
if ($LASTEXITCODE -eq 0) {
|
if ($LASTEXITCODE -eq 0) {
|
||||||
$sourceVideoCodec = ($ffprobeVideoCodecOutput | Select-Object -First 1).Trim().ToLowerInvariant()
|
$vCodecLine = $ffprobeVideoCodecOutput | Select-Object -First 1
|
||||||
|
if ($vCodecLine) {
|
||||||
|
$sourceVideoCodec = ([string]$vCodecLine).Trim().ToLowerInvariant()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$ffprobeAudioCodecOutput = & ffprobe -v error -select_streams a:0 -show_entries stream=codec_name -of default=noprint_wrappers=1:nokey=1 -- $SourcePath 2>&1
|
$ffprobeAudioCodecOutput = & ffprobe -v error -select_streams a:0 -show_entries stream=codec_name -of default=noprint_wrappers=1:nokey=1 -- $SourcePath 2>&1
|
||||||
$sourceAudioCodec = ''
|
$sourceAudioCodec = ''
|
||||||
if ($LASTEXITCODE -eq 0) {
|
if ($LASTEXITCODE -eq 0) {
|
||||||
$sourceAudioCodec = ($ffprobeAudioCodecOutput | Select-Object -First 1).Trim().ToLowerInvariant()
|
$aCodecLine = $ffprobeAudioCodecOutput | Select-Object -First 1
|
||||||
|
if ($aCodecLine) {
|
||||||
|
$sourceAudioCodec = ([string]$aCodecLine).Trim().ToLowerInvariant()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# A source codec not on the target container's stream-copy list forces a re-encode
|
# A source codec not on the target container's stream-copy list forces a re-encode
|
||||||
@@ -364,6 +386,9 @@ if ($scaleWidth -gt 0) {
|
|||||||
if ($FPS -gt 0) {
|
if ($FPS -gt 0) {
|
||||||
Write-Host (' FPS: {0}' -f $FPS)
|
Write-Host (' FPS: {0}' -f $FPS)
|
||||||
}
|
}
|
||||||
|
if ($Reverse) {
|
||||||
|
Write-Host ' Reverse: video reversed, audio unchanged'
|
||||||
|
}
|
||||||
Write-Host (' Target: {0}' -f $TargetPath)
|
Write-Host (' Target: {0}' -f $TargetPath)
|
||||||
|
|
||||||
$ffmpegArgs = @(
|
$ffmpegArgs = @(
|
||||||
@@ -375,7 +400,7 @@ if ($AudioPath) {
|
|||||||
$ffmpegArgs += @('-i', $AudioPath)
|
$ffmpegArgs += @('-i', $AudioPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
$needsReencode = $scaleWidth -gt 0 -or $FPS -gt 0 -or $videoCodecMismatch
|
$needsReencode = $scaleWidth -gt 0 -or $FPS -gt 0 -or $videoCodecMismatch -or $Reverse
|
||||||
|
|
||||||
if ($StartFrame -gt 0 -or $actualEndFrame -lt $maxFrameIndex) {
|
if ($StartFrame -gt 0 -or $actualEndFrame -lt $maxFrameIndex) {
|
||||||
$filterParts = @('select=between(n\,{0}\,{1})' -f $StartFrame, $actualEndFrame)
|
$filterParts = @('select=between(n\,{0}\,{1})' -f $StartFrame, $actualEndFrame)
|
||||||
@@ -386,6 +411,9 @@ if ($StartFrame -gt 0 -or $actualEndFrame -lt $maxFrameIndex) {
|
|||||||
if ($FPS -gt 0) {
|
if ($FPS -gt 0) {
|
||||||
$filterParts += ('fps={0}' -f $FPS)
|
$filterParts += ('fps={0}' -f $FPS)
|
||||||
}
|
}
|
||||||
|
if ($Reverse) {
|
||||||
|
$filterParts += 'reverse'
|
||||||
|
}
|
||||||
$videoFilter = $filterParts -join ','
|
$videoFilter = $filterParts -join ','
|
||||||
$ffmpegArgs += @(
|
$ffmpegArgs += @(
|
||||||
'-vf', $videoFilter
|
'-vf', $videoFilter
|
||||||
@@ -410,6 +438,9 @@ if ($StartFrame -gt 0 -or $actualEndFrame -lt $maxFrameIndex) {
|
|||||||
if ($FPS -gt 0) {
|
if ($FPS -gt 0) {
|
||||||
$filterParts += ('fps={0}' -f $FPS)
|
$filterParts += ('fps={0}' -f $FPS)
|
||||||
}
|
}
|
||||||
|
if ($Reverse) {
|
||||||
|
$filterParts += 'reverse'
|
||||||
|
}
|
||||||
if ($filterParts.Count -gt 0) {
|
if ($filterParts.Count -gt 0) {
|
||||||
$videoFilter = $filterParts -join ','
|
$videoFilter = $filterParts -join ','
|
||||||
$ffmpegArgs += @('-vf', $videoFilter)
|
$ffmpegArgs += @('-vf', $videoFilter)
|
||||||
|
|||||||
+14
-3
@@ -6,7 +6,7 @@
|
|||||||
.PARAMETER Target
|
.PARAMETER Target
|
||||||
Target directory for cropped video files.
|
Target directory for cropped video files.
|
||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
Crops video clips from 1928x1076 or 1926x1076 to 1920x1080 using ffmpeg.
|
Crops video clips from 1928x1076 or 1926x1076 to 1920x1080, or from 1284x716 to 1280x720 using ffmpeg.
|
||||||
Supports -WhatIf and -Confirm for safe execution.
|
Supports -WhatIf and -Confirm for safe execution.
|
||||||
#>
|
#>
|
||||||
[CmdletBinding(SupportsShouldProcess)]
|
[CmdletBinding(SupportsShouldProcess)]
|
||||||
@@ -113,7 +113,8 @@ foreach ($video in $videos) {
|
|||||||
$inAudioText = if ($inInfo.HasAudio) { 'yes' } else { 'no' }
|
$inAudioText = if ($inInfo.HasAudio) { 'yes' } else { 'no' }
|
||||||
|
|
||||||
$isValidResolution = ($inInfo.Width -eq 1928 -and $inInfo.Height -eq 1076) -or
|
$isValidResolution = ($inInfo.Width -eq 1928 -and $inInfo.Height -eq 1076) -or
|
||||||
($inInfo.Width -eq 1926 -and $inInfo.Height -eq 1076)
|
($inInfo.Width -eq 1926 -and $inInfo.Height -eq 1076) -or
|
||||||
|
($inInfo.Width -eq 1284 -and $inInfo.Height -eq 716)
|
||||||
|
|
||||||
if (-not $isValidResolution) {
|
if (-not $isValidResolution) {
|
||||||
Write-Host "Skipping: $($inInfo.FileName) (resolution $inRes)"
|
Write-Host "Skipping: $($inInfo.FileName) (resolution $inRes)"
|
||||||
@@ -132,10 +133,20 @@ foreach ($video in $videos) {
|
|||||||
$outCropPath = Join-Path $targetFull ($video.BaseName + '.crop.mp4')
|
$outCropPath = Join-Path $targetFull ($video.BaseName + '.crop.mp4')
|
||||||
$outFinalPath = Join-Path $targetFull $video.Name
|
$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 = @(
|
$ffArgs = @(
|
||||||
'-y',
|
'-y',
|
||||||
'-i', $inputPath,
|
'-i', $inputPath,
|
||||||
'-vf', 'scale=-2:1080,crop=1920:1080:(in_w-1920)/2:(in_h-1080)/2'
|
'-vf', $videoFilter
|
||||||
)
|
)
|
||||||
|
|
||||||
if ($inInfo.HasAudio) {
|
if ($inInfo.HasAudio) {
|
||||||
|
|||||||
Reference in New Issue
Block a user