Change -Suffix parameter in Create-Clip.ps1 from optional empty string to required non-empty string with default value "-Clip", removing the hardcoded "-Clip" prefix from target name derivation so suffix is inserted directly before extension (e.g. default behavior unchanged: P01.mp4 -> P01-Clip.mp4, but custom -Suffix "-Shot-1" now makes P01.mp4 -> P01-Shot-1.mp4 instead of P01-Clip-Shot-1.mp4), add ValidateNotNullOrEmpty attribute, update documentation to reflect new default and behavior, and add installation
This commit is contained in:
1 parent
61894433d9
commit
4cc09d6369
4 files changed
+323
-13
No files matched your search
+9
-8
@@ -1,8 +1,8 @@
|
|||||||
<#
|
<#
|
||||||
.SYNOPSIS
|
.SYNOPSIS
|
||||||
Creates a new clipped video file using Clone-Video.ps1, requiring the -Video and -Frames parameters.
|
Creates a new clipped video file using Clone-Video.ps1, requiring the -Video and -Frames parameters.
|
||||||
The filename used for the -Target parameter is automatically derived from the -Video filename, with "-Clip" inserted before the video file extension (e.g., P01.mp4 becomes P01-Clip.mp4).
|
The filename used for the -Target parameter is automatically derived from the -Video filename, with the -Suffix value (default "-Clip") inserted before the video file extension (e.g., P01.mp4 becomes P01-Clip.mp4).
|
||||||
When the video name carries a '.skip' marker before its extension, "-Clip" is inserted before that marker so it stays last (e.g., P01.skip.mp4 becomes P01-Clip.skip.mp4).
|
When the video name carries a '.skip' marker before its extension, the suffix is inserted before that marker so it stays last (e.g., P01.skip.mp4 becomes P01-Clip.skip.mp4).
|
||||||
Any extra parameters will be pass directly to Clone-Video.ps1, except for parameters -Video, -Frames, -Suffix and -Target.
|
Any extra parameters will be pass directly to Clone-Video.ps1, except for parameters -Video, -Frames, -Suffix and -Target.
|
||||||
.PARAMETER Video
|
.PARAMETER Video
|
||||||
Path to the input video file. Can be absolute or relative to current directory. If the path has no extension, .mp4, .webm and .mkv are tried in turn.
|
Path to the input video file. Can be absolute or relative to current directory. If the path has no extension, .mp4, .webm and .mkv are tried in turn.
|
||||||
@@ -14,8 +14,8 @@
|
|||||||
- %: Represents the last frame index. Can be used alone or in ranges (e.g., "%", "10-%", "-%")
|
- %: Represents the last frame index. Can be used alone or in ranges (e.g., "%", "10-%", "-%")
|
||||||
- all: Extract all frames (same as "0-%")
|
- all: Extract all frames (same as "0-%")
|
||||||
.PARAMETER Suffix
|
.PARAMETER Suffix
|
||||||
Optional text appended to the derived -Target name after "-Clip" and before the file extension
|
Text inserted into the derived -Target name before the file extension. Defaults to "-Clip" and cannot be empty.
|
||||||
(e.g. -Suffix "-Shot-1" makes P01.mp4 become P01-Clip-Shot-1.mp4). A '.skip' marker still stays last.
|
Example: -Suffix "-Clip-Shot-1" makes P01.mp4 become P01-Clip-Shot-1.mp4. A '.skip' marker still stays last.
|
||||||
#>
|
#>
|
||||||
[CmdletBinding(SupportsShouldProcess)]
|
[CmdletBinding(SupportsShouldProcess)]
|
||||||
param(
|
param(
|
||||||
@@ -26,7 +26,8 @@ param(
|
|||||||
[string]$Frames,
|
[string]$Frames,
|
||||||
|
|
||||||
[Parameter(Mandatory = $false)]
|
[Parameter(Mandatory = $false)]
|
||||||
[string]$Suffix = '',
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string]$Suffix = '-Clip',
|
||||||
|
|
||||||
[Parameter(ValueFromRemainingArguments)]
|
[Parameter(ValueFromRemainingArguments)]
|
||||||
[object[]]$ExtraArgs
|
[object[]]$ExtraArgs
|
||||||
@@ -50,12 +51,12 @@ if (-not (Test-Path -LiteralPath $videoPath)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Derive the -Target path: insert "-Clip" before the file extension (e.g., P01.mp4 -> P01-Clip.mp4)
|
# Derive the -Target path: insert -Suffix before the file extension (e.g., P01.mp4 -> P01-Clip.mp4)
|
||||||
$sourceExt = [System.IO.Path]::GetExtension($videoPath)
|
$sourceExt = [System.IO.Path]::GetExtension($videoPath)
|
||||||
$sourceBase = [System.IO.Path]::GetFileNameWithoutExtension($videoPath)
|
$sourceBase = [System.IO.Path]::GetFileNameWithoutExtension($videoPath)
|
||||||
$sourceDir = [System.IO.Path]::GetDirectoryName($videoPath)
|
$sourceDir = [System.IO.Path]::GetDirectoryName($videoPath)
|
||||||
|
|
||||||
# A '.skip' marker must stay immediately before the container extension, so "-Clip" is
|
# A '.skip' marker must stay immediately before the container extension, so the suffix is
|
||||||
# inserted before it rather than after (e.g., P01.skip.mp4 -> P01-Clip.skip.mp4).
|
# inserted before it rather than after (e.g., P01.skip.mp4 -> P01-Clip.skip.mp4).
|
||||||
# The marker's original casing is preserved.
|
# The marker's original casing is preserved.
|
||||||
$skipMarker = ''
|
$skipMarker = ''
|
||||||
@@ -64,7 +65,7 @@ if ($sourceBase -match '^(.*)(\.skip)$') {
|
|||||||
$skipMarker = $Matches[2]
|
$skipMarker = $Matches[2]
|
||||||
}
|
}
|
||||||
|
|
||||||
$targetName = "$sourceBase-Clip$Suffix$skipMarker$sourceExt"
|
$targetName = "$sourceBase$Suffix$skipMarker$sourceExt"
|
||||||
$targetPath = if ([string]::IsNullOrEmpty($sourceDir)) { $targetName } else { Join-Path $sourceDir $targetName }
|
$targetPath = if ([string]::IsNullOrEmpty($sourceDir)) { $targetName } else { Join-Path $sourceDir $targetName }
|
||||||
|
|
||||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
@echo off
|
||||||
|
pwsh.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0Create-Scenes.ps1" %*
|
||||||
@@ -0,0 +1,268 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Detects scenes in a video file with ffmpeg and extracts each scene as a separate video clip file.
|
||||||
|
.DESCRIPTION
|
||||||
|
Analyzes the input video with ffmpeg scene-change detection, using the -Scene threshold, and computes
|
||||||
|
the first and last frame (0-based) of each detected scene. Each scene is then extracted by calling
|
||||||
|
Create-Clip.ps1 (located in the same directory as this script) with the scene frame range, which in
|
||||||
|
turn calls Clone-Video.ps1.
|
||||||
|
|
||||||
|
Clip files are always saved in the same directory as the input video. The clip filename is the input
|
||||||
|
video filename plus the scene suffix, with the scene number starting at 1
|
||||||
|
(ex: P01.mp4 scene 1 becomes P01-Scene-01.mp4). For sources carrying a .skip marker, the marker stays
|
||||||
|
last, as handled by Create-Clip.ps1 (ex: P01.skip.mp4 becomes P01-Scene-01.skip.mp4).
|
||||||
|
|
||||||
|
Encoding is performed by Clone-Video.ps1: by default each clip keeps the resolution, frame rate and
|
||||||
|
compression characteristics of the input video, unless overridden with extra parameters (ex: -Size,
|
||||||
|
-FPS, -CRF) forwarded to Create-Clip.ps1.
|
||||||
|
|
||||||
|
The list of scenes (index, start frame, end frame, frame count, start time, end time) is always displayed.
|
||||||
|
If no scene change is detected, the whole video is treated as a single scene.
|
||||||
|
|
||||||
|
Unless -List is specified, existing scene clip files from a previous run (same directory, same source
|
||||||
|
name, same suffix pattern and same extension) are deleted before the new clips are created. Existing
|
||||||
|
files are always replaced.
|
||||||
|
|
||||||
|
Scene detection and the scene list are read-only: they always run, including with -List and -WhatIf.
|
||||||
|
Only the deletion and the creation of files are subject to -WhatIf and -Confirm. Both switches are
|
||||||
|
also forwarded to Create-Clip.ps1.
|
||||||
|
.PARAMETER Video
|
||||||
|
Path to the input video file. Can be absolute or relative to current directory. If the path has no
|
||||||
|
extension, .mp4, .webm and .mkv are tried in turn.
|
||||||
|
.PARAMETER Scene
|
||||||
|
Threshold value between 0 and 1.0 to determine scene changes. Lower values mean more sensitive
|
||||||
|
detection (more scenes). Default is 0.3.
|
||||||
|
.PARAMETER Suffix
|
||||||
|
Scene suffix to use for the output video clip files. Must contain a printf-style number placeholder
|
||||||
|
such as %02d, which is replaced by the scene number starting at 1. By default, "-Scene-%02d" is used.
|
||||||
|
.PARAMETER List
|
||||||
|
Only displays the list of scenes. No video clip file is created and no existing file is deleted.
|
||||||
|
.PARAMETER ExtraArgs
|
||||||
|
Additional parameters passed as-is to Create-Clip.ps1 and from there to Clone-Video.ps1
|
||||||
|
(ex: -Size 720p -NoAudio -CRF 18 -LTX). The parameters -Video, -Frames, -Suffix and -Target are
|
||||||
|
managed by this script and must not be repeated.
|
||||||
|
.EXAMPLE
|
||||||
|
.\Create-Scenes.ps1 -Video P01.mp4 -Scene 0.4 -List
|
||||||
|
Lists the scenes detected in P01.mp4 without creating or deleting any file.
|
||||||
|
.EXAMPLE
|
||||||
|
.\Create-Scenes.ps1 -Video P01 -Scene 0.3 -Size 720p -NoAudio
|
||||||
|
Creates P01-Scene-01.mp4, P01-Scene-02.mp4, ... scaled to 720p and without audio.
|
||||||
|
.EXAMPLE
|
||||||
|
.\Create-Scenes.ps1 -Video P01.mp4 -Suffix "-Shot-%03d" -WhatIf
|
||||||
|
Shows which files would be deleted and created, without changing anything.
|
||||||
|
#>
|
||||||
|
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true, Position = 0)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string]$Video,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[ValidateRange(0.0, 1.0)]
|
||||||
|
[double]$Scene = 0.3,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[ValidatePattern('%0?\d*d')]
|
||||||
|
[string]$Suffix = '-Scene-%02d',
|
||||||
|
|
||||||
|
[switch]$List,
|
||||||
|
|
||||||
|
[Parameter(ValueFromRemainingArguments)]
|
||||||
|
[object[]]$ExtraArgs
|
||||||
|
)
|
||||||
|
|
||||||
|
Set-StrictMode -Version Latest
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
$invariant = [Globalization.CultureInfo]::InvariantCulture
|
||||||
|
|
||||||
|
function Resolve-VideoPath {
|
||||||
|
param([string]$Path)
|
||||||
|
$candidates = @($Path) + @('.mp4', '.webm', '.mkv' | ForEach-Object { "$Path$_" })
|
||||||
|
$found = $candidates | Where-Object { Test-Path -LiteralPath $_ -PathType Leaf } | Select-Object -First 1
|
||||||
|
if (-not $found) { throw "Video file not found: $Path" }
|
||||||
|
(Resolve-Path -LiteralPath $found).Path
|
||||||
|
}
|
||||||
|
|
||||||
|
function ConvertTo-Number {
|
||||||
|
param([string]$Text)
|
||||||
|
# Parses plain numbers and ffprobe rationals (e.g. "30000/1001"). Returns $null for N/A or invalid values.
|
||||||
|
$parts = $Text -split '/'
|
||||||
|
$num = 0.0
|
||||||
|
$den = 1.0
|
||||||
|
if ($parts.Count -gt 2 -or -not [double]::TryParse($parts[0], [Globalization.NumberStyles]::Float, $invariant, [ref]$num)) { return $null }
|
||||||
|
if ($parts.Count -eq 2 -and (-not [double]::TryParse($parts[1], [Globalization.NumberStyles]::Float, $invariant, [ref]$den) -or $den -eq 0)) { return $null }
|
||||||
|
$num / $den
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-VideoInfo {
|
||||||
|
param([string]$Path)
|
||||||
|
$ffArgs = @('-hide_banner', '-v', 'error', '-count_frames', '-select_streams', 'v:0',
|
||||||
|
'-show_entries', 'stream=avg_frame_rate,r_frame_rate,start_time,nb_read_frames,nb_frames,duration',
|
||||||
|
'-of', 'default=noprint_wrappers=1', $Path)
|
||||||
|
Write-Verbose ('ffprobe ' + ($ffArgs -join ' '))
|
||||||
|
$lines = & ffprobe @ffArgs
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "ffprobe failed to read $Path" }
|
||||||
|
|
||||||
|
$fields = @{}
|
||||||
|
foreach ($line in $lines) {
|
||||||
|
if ("$line" -match '^(\w+)=(.*)$') { $fields[$Matches[1]] = $Matches[2] }
|
||||||
|
}
|
||||||
|
|
||||||
|
$fps = ConvertTo-Number $fields['avg_frame_rate']
|
||||||
|
if (-not $fps) { $fps = ConvertTo-Number $fields['r_frame_rate'] }
|
||||||
|
if (-not $fps) { throw "Cannot read the frame rate of $Path" }
|
||||||
|
|
||||||
|
$startTime = ConvertTo-Number $fields['start_time']
|
||||||
|
if ($null -eq $startTime) { $startTime = 0.0 }
|
||||||
|
|
||||||
|
$frames = ConvertTo-Number $fields['nb_read_frames']
|
||||||
|
if ($null -eq $frames) { $frames = ConvertTo-Number $fields['nb_frames'] }
|
||||||
|
if ($null -eq $frames) { $frames = [Math]::Round((ConvertTo-Number $fields['duration']) * $fps) }
|
||||||
|
if ($frames -lt 1) { throw "Cannot determine the frame count of $Path" }
|
||||||
|
|
||||||
|
[pscustomobject]@{ Fps = $fps; StartTime = $startTime; TotalFrames = [int][Math]::Round($frames) }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-SceneChangeTime {
|
||||||
|
param([string]$Path, [double]$Threshold)
|
||||||
|
$filter = "select='gt(scene,{0})',showinfo" -f $Threshold.ToString($invariant)
|
||||||
|
$ffArgs = @('-hide_banner', '-nostats', '-loglevel', 'info', '-i', $Path, '-vf', $filter, '-an', '-f', 'null', '-')
|
||||||
|
Write-Verbose ('ffmpeg ' + ($ffArgs -join ' '))
|
||||||
|
$output = & ffmpeg @ffArgs 2>&1 | Out-String
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "ffmpeg scene detection failed on $Path" }
|
||||||
|
[regex]::Matches($output, 'pts_time:([\d.]+)') | ForEach-Object { [double]::Parse($_.Groups[1].Value, $invariant) }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Format-SceneSuffix {
|
||||||
|
param([string]$Template, [int]$Number)
|
||||||
|
# Replaces only the first printf-style placeholder (%d, %2d, %02d, %003d) with the zero-padded number.
|
||||||
|
$placeholder = [regex]::Match($Template, '%0*(\d*)d')
|
||||||
|
$width = [Math]::Max(1, [int]('0' + $placeholder.Groups[1].Value))
|
||||||
|
$Template.Substring(0, $placeholder.Index) + $Number.ToString('D' + $width, $invariant) +
|
||||||
|
$Template.Substring($placeholder.Index + $placeholder.Length)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-SceneList {
|
||||||
|
param($Info, [double[]]$ChangeTimes, [string]$Template)
|
||||||
|
# pts_time * fps is exact only for constant frame rate videos; with variable frame rate,
|
||||||
|
# scene boundaries can be off by one frame.
|
||||||
|
$boundaries = @($ChangeTimes | ForEach-Object { [int][Math]::Round(($_ - $Info.StartTime) * $Info.Fps) } |
|
||||||
|
Where-Object { $_ -gt 0 -and $_ -lt $Info.TotalFrames } | Sort-Object -Unique)
|
||||||
|
$starts = @(0) + $boundaries
|
||||||
|
for ($i = 0; $i -lt $starts.Count; $i++) {
|
||||||
|
$start = [int]$starts[$i]
|
||||||
|
$end = if ($i + 1 -lt $starts.Count) { $starts[$i + 1] - 1 } else { $Info.TotalFrames - 1 }
|
||||||
|
[pscustomobject]@{
|
||||||
|
Index = $i + 1
|
||||||
|
StartFrame = $start
|
||||||
|
EndFrame = $end
|
||||||
|
Frames = $end - $start + 1
|
||||||
|
StartTime = [Math]::Round($start / $Info.Fps, 3)
|
||||||
|
EndTime = [Math]::Round($end / $Info.Fps, 3)
|
||||||
|
Suffix = Format-SceneSuffix -Template $Template -Number ($i + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-SceneFilePattern {
|
||||||
|
param([string]$VideoPath, [string]$Template)
|
||||||
|
# Regex matching the clip files this script creates for this source; used to find files of previous runs.
|
||||||
|
$ext = [IO.Path]::GetExtension($VideoPath)
|
||||||
|
$stem = [IO.Path]::GetFileNameWithoutExtension($VideoPath)
|
||||||
|
$skipMarker = ''
|
||||||
|
if ($stem -match '^(.*)(\.skip)$') {
|
||||||
|
$stem = $Matches[1]
|
||||||
|
$skipMarker = '\.skip'
|
||||||
|
}
|
||||||
|
$placeholder = [regex]::Match($Template, '%0*\d*d')
|
||||||
|
$suffixPattern = [regex]::Escape($Template.Substring(0, $placeholder.Index)) + '\d+' +
|
||||||
|
[regex]::Escape($Template.Substring($placeholder.Index + $placeholder.Length))
|
||||||
|
'^' + [regex]::Escape($stem) + $suffixPattern + $skipMarker + [regex]::Escape($ext) + '$'
|
||||||
|
}
|
||||||
|
|
||||||
|
function Remove-ExistingScene {
|
||||||
|
[CmdletBinding(SupportsShouldProcess)]
|
||||||
|
param([string]$VideoPath, [string]$Pattern)
|
||||||
|
$directory = [IO.Path]::GetDirectoryName($VideoPath)
|
||||||
|
$existing = Get-ChildItem -LiteralPath $directory -File |
|
||||||
|
Where-Object { $_.Name -match $Pattern -and $_.FullName -ne $VideoPath }
|
||||||
|
foreach ($file in $existing) {
|
||||||
|
if ($PSCmdlet.ShouldProcess($file.FullName, 'Delete existing scene file')) {
|
||||||
|
Remove-Item -LiteralPath $file.FullName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ConvertTo-ClipArgument {
|
||||||
|
param([object[]]$Tokens)
|
||||||
|
# Validates the extra arguments: every value must follow a parameter name, and parameter names without a value are switches.
|
||||||
|
$params = @{}
|
||||||
|
$pending = $null
|
||||||
|
foreach ($token in $Tokens) {
|
||||||
|
if ($token -is [string] -and $token -match '^-([A-Za-z]\w*)(:(.*))?$') {
|
||||||
|
if ($pending) { $params[$pending] = $true }
|
||||||
|
$pending = $Matches[1]
|
||||||
|
if ($Matches[2]) {
|
||||||
|
$params[$pending] = $Matches[3]
|
||||||
|
$pending = $null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif ($pending) {
|
||||||
|
$params[$pending] = $token
|
||||||
|
$pending = $null
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw "Unexpected argument '$token'. Extra parameters must be passed by name (e.g. -Size 720p)."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($pending) { $params[$pending] = $true }
|
||||||
|
$params
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($tool in 'ffmpeg', 'ffprobe') {
|
||||||
|
if (-not (Get-Command $tool -ErrorAction SilentlyContinue)) { throw "$tool was not found in PATH." }
|
||||||
|
}
|
||||||
|
$createClipPath = Join-Path $PSScriptRoot 'Create-Clip.ps1'
|
||||||
|
if (-not (Test-Path -LiteralPath $createClipPath -PathType Leaf)) {
|
||||||
|
throw "Create-Clip.ps1 was not found next to this script: $createClipPath"
|
||||||
|
}
|
||||||
|
|
||||||
|
$videoPath = Resolve-VideoPath -Path $Video
|
||||||
|
[object[]]$tokens = @()
|
||||||
|
if ($null -ne $ExtraArgs) { $tokens = @($ExtraArgs) }
|
||||||
|
$extraParams = ConvertTo-ClipArgument -Tokens $tokens
|
||||||
|
foreach ($reserved in 'Video', 'Frames', 'Suffix', 'Target') {
|
||||||
|
if ($extraParams.ContainsKey($reserved)) {
|
||||||
|
throw "-$reserved is managed by Create-Scenes.ps1 and must not be passed as an extra parameter."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$info = Get-VideoInfo -Path $videoPath
|
||||||
|
$changeTimes = @(Get-SceneChangeTime -Path $videoPath -Threshold $Scene)
|
||||||
|
$scenes = @(Get-SceneList -Info $info -ChangeTimes $changeTimes -Template $Suffix)
|
||||||
|
$scenes | Format-Table -AutoSize | Out-Host
|
||||||
|
if ($List) { return }
|
||||||
|
|
||||||
|
$confirm = [bool]$PSBoundParameters['Confirm']
|
||||||
|
$pattern = Get-SceneFilePattern -VideoPath $videoPath -Template $Suffix
|
||||||
|
Remove-ExistingScene -VideoPath $videoPath -Pattern $pattern -WhatIf:$WhatIfPreference -Confirm:$confirm
|
||||||
|
|
||||||
|
$failures = 0
|
||||||
|
foreach ($clipScene in $scenes) {
|
||||||
|
$frames = '{0}-{1}' -f $clipScene.StartFrame, $clipScene.EndFrame
|
||||||
|
Write-Verbose ('Scene {0}: frames {1}, suffix {2}' -f $clipScene.Index, $frames, $clipScene.Suffix)
|
||||||
|
# Extra parameters are forwarded as the original token array, not as the hashtable: Create-Clip.ps1
|
||||||
|
# collects unknown parameters through ValueFromRemainingArguments and parses them itself, while a
|
||||||
|
# hashtable splat would hand it '-Name:value' tokens it does not understand.
|
||||||
|
try {
|
||||||
|
& $createClipPath -Video $videoPath -Frames $frames -Suffix $clipScene.Suffix -WhatIf:$WhatIfPreference -Confirm:$confirm @tokens
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "Create-Clip.ps1 exited with code $LASTEXITCODE" }
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Warning ('Scene {0} failed: {1}' -f $clipScene.Index, $_.Exception.Message)
|
||||||
|
$failures++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($failures -gt 0) { throw "$failures scene(s) failed to create." }
|
||||||
@@ -7,7 +7,8 @@ A collection of PowerShell scripts for video processing tasks using ffmpeg and f
|
|||||||
- **PowerShell 7+** (pwsh.exe)
|
- **PowerShell 7+** (pwsh.exe)
|
||||||
- **ffmpeg** - Must be in system PATH
|
- **ffmpeg** - Must be in system PATH
|
||||||
- **ffprobe** - Must be in system PATH
|
- **ffprobe** - Must be in system PATH
|
||||||
- **rife-ncnn-vulkan** - Must be in system PATH
|
- **rife-ncnn-vulkan** - Must be in system PATH and also defined the environment variable **RIFE_HOME** to the same directory
|
||||||
|
- **upscayl-bin** - Must be in system PATH and also defined the environment variable **UPSCAYL_HOME** to the same directory
|
||||||
|
|
||||||
## ffmpeg
|
## ffmpeg
|
||||||
|
|
||||||
@@ -15,7 +16,31 @@ To install ffmpeg on Windows, download it from [ffmpeg.org](https://ffmpeg.org/d
|
|||||||
|
|
||||||
## rife-ncnn-vulkan
|
## rife-ncnn-vulkan
|
||||||
|
|
||||||
To install rife-ncnn-vulkan on Windows, download it from [rife-ncnn-vulkan](https://github.com/nihui/rife-ncnn-vulkan) and add it to your system PATH.
|
To install rife-ncnn-vulkan on Windows, see the [RIFE 4.26 installation guide](RIFE.md). Add it to your system PATH.
|
||||||
|
|
||||||
|
## upscayl-bin
|
||||||
|
|
||||||
|
To install upscayl-bin on Windows, see the [Upscalers installation guide](Upscalers.md). Add it to your system PATH.
|
||||||
|
|
||||||
|
## NCNN_GPU_ID
|
||||||
|
|
||||||
|
The environment variable **NCNN_GPU_ID** is used to specify the GPU device ID for ncnn-based applications.
|
||||||
|
|
||||||
|
This environment variable must be set for both `rife-ncnn-vulkan` and `upscayl-bin`.
|
||||||
|
|
||||||
|
The value stored in **NCNN_GPU_ID** will be either:
|
||||||
|
|
||||||
|
- **`0`** — use the first Vulkan-capable GPU (device index 0). If you have multiple GPUs, they'd be `0`, `1`, `2`, etc.
|
||||||
|
- **`-1`** — use CPU only (no Vulkan GPU is available).
|
||||||
|
|
||||||
|
These map directly to the `-g` flag values accepted by both `rife-ncnn-vulkan` and `upscayl-bin`:
|
||||||
|
|
||||||
|
| `-g` value | Meaning |
|
||||||
|
| --- | --- |
|
||||||
|
| `0` | First GPU |
|
||||||
|
| `1` | Second GPU |
|
||||||
|
| `2` | Third GPU |
|
||||||
|
| `-1` | CPU |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -23,15 +48,20 @@ To install rife-ncnn-vulkan on Windows, download it from [rife-ncnn-vulkan](http
|
|||||||
|
|
||||||
| Script | Purpose |
|
| Script | Purpose |
|
||||||
| :--- | :--- |
|
| :--- | :--- |
|
||||||
|
| Clone-Image.ps1 | Clone/copy image files with optional resizing (cover-scale then crop), format conversion, and quality/compression settings |
|
||||||
| Clone-Video.ps1 | Clone/copy video files with optional frame range and sequence extraction |
|
| Clone-Video.ps1 | Clone/copy video files with optional frame range and sequence extraction |
|
||||||
| 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-Clip.ps1 | Create a clip of a frame range from a video, named from the source with a suffix (default `-Clip`) |
|
||||||
| 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-PillarBox.ps1 | Wrap a video in a 16:9 frame with selectable side-bar effects (black, white, blurred-background variants) |
|
||||||
|
| Create-Scenes.ps1 | Detect scene changes with ffmpeg and extract each scene as a separate clip using Create-Clip.ps1 |
|
||||||
| 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 |
|
||||||
| Create-Turnaround.ps1 | Combine a sequence of images into a single turnaround (character reference sheet) image, in a row or a grid |
|
| Create-Turnaround.ps1 | Combine a sequence of images into a single turnaround (character reference sheet) image, in a row or a grid |
|
||||||
| 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-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) |
|
||||||
| Get-Frames.ps1 | Extract a range of frames to PNG images |
|
| Get-Frame.ps1 | Extract a specific frame of a video to PNG, with the image name derived from the video and frame index |
|
||||||
| 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-FramesRange.ps1 | Extract a range of frames to PNG images |
|
||||||
|
| Get-LastFrame.ps1 | Extract a specific frame or last frame to PNG, with an optional filename pattern |
|
||||||
| Get-VideoAudio.ps1 | Extracts audio in either WAV or MP3 format from a video file |
|
| Get-VideoAudio.ps1 | Extracts audio in either WAV or MP3 format from a video file |
|
||||||
| Get-VideoInfo.ps1 | Extract and display video metadata from comment tags |
|
| Get-VideoInfo.ps1 | Extract and display video metadata from comment tags |
|
||||||
| Stich-InBetween.ps1 | Generate interpolated in-between frames between two images using RIFE and encode to video |
|
| Stich-InBetween.ps1 | Generate interpolated in-between frames between two images using RIFE and encode to video |
|
||||||
@@ -43,7 +73,16 @@ To install rife-ncnn-vulkan on Windows, download it from [rife-ncnn-vulkan](http
|
|||||||
|
|
||||||
## Scripts Installation
|
## Scripts Installation
|
||||||
|
|
||||||
The scripts are available from **git.gurusushi.dyndns.org**, repository: **guru/Video-Processing-Scripts**.
|
The scripts are available from two locations:
|
||||||
|
|
||||||
|
- SVN: **public-svn**, project: **Video Processing Scripts**.
|
||||||
|
- Git: **git.gurusushi.dyndns.org**, repository: **guru/Video-Processing-Scripts**.
|
||||||
|
|
||||||
|
```bash title="Clone the repository from Git"
|
||||||
|
git clone https://git.gurusushi.dyndns.org/guru/Video-Processing-Scripts.git
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: there is a `Update-Video-Processing-Scripts.cmd` script that can be used to update the directory from Git to get the latest version.
|
||||||
|
|
||||||
Add the project folder to your PATH environment variable to access the scripts from any location.
|
Add the project folder to your PATH environment variable to access the scripts from any location.
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user