Rename -Scene parameter to -Level in Create-Scenes.ps1, add -Scenes parameter for automatic level search to detect exact scene count with binary search algorithm, update documentation across Create-Scenes.md, SKILL.md, README.md and index.md to explain search behavior and exit code 1 on failure, fix agent skills directory paths to use Windows environment variables and add VS Code location
This commit is contained in:
1 parent
60feb778d3
commit
4378e3fd99
5 files changed
+103
-30
No files matched your search
+62
-14
@@ -2,7 +2,7 @@
|
||||
.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
|
||||
Analyzes the input video with ffmpeg scene-change detection, using the -Level 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.
|
||||
@@ -18,6 +18,9 @@
|
||||
|
||||
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.
|
||||
With -Scenes, the level is adjusted automatically until the requested number of scenes is detected; every
|
||||
attempt is displayed. If that number cannot be reached, the script stops with exit code 1 and creates or
|
||||
deletes nothing.
|
||||
|
||||
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
|
||||
@@ -29,9 +32,12 @@
|
||||
.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 Level
|
||||
Threshold level value between 0 and 1.0 to determine scene changes. Lower values mean more sensitive
|
||||
detection (more scenes). Default is 0.3. The value is rounded to 2 decimals.
|
||||
.PARAMETER Scenes
|
||||
Target number of scenes (a positive integer). The level is searched automatically, starting from -Level,
|
||||
until exactly this many scenes are detected. Stops with exit code 1 if the number cannot be reached.
|
||||
.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.
|
||||
@@ -39,13 +45,16 @@
|
||||
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.
|
||||
(ex: -Size 720p -NoAudio -CRF 18 -LTX). The parameters -Video, -Frames, -Suffix, -Target, -Level and
|
||||
-Scenes are managed by this script and must not be repeated.
|
||||
.EXAMPLE
|
||||
.\Create-Scenes.ps1 -Video P01.mp4 -Scene 0.4 -List
|
||||
.\Create-Scenes.ps1 -Video P01.mp4 -Level 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
|
||||
.\Create-Scenes.ps1 -Video P01.mp4 -Scenes 4 -List
|
||||
Searches the level automatically and lists the scenes once exactly 4 scenes are detected.
|
||||
.EXAMPLE
|
||||
.\Create-Scenes.ps1 -Video P01 -Level 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
|
||||
@@ -59,7 +68,11 @@ param(
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[ValidateRange(0.0, 1.0)]
|
||||
[double]$Scene = 0.3,
|
||||
[double]$Level = 0.3,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[ValidateRange(1, [int]::MaxValue)]
|
||||
[int]$Scenes,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
@@ -234,16 +247,51 @@ $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') {
|
||||
foreach ($reserved in 'Video', 'Frames', 'Suffix', 'Target', 'Level', 'Scenes') {
|
||||
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
|
||||
# Level rounded to 2 decimals. The search rounds each new level to 2 decimals too; the step is not rounded.
|
||||
$sceneLevel = [Math]::Round($Level, 2, [MidpointRounding]::AwayFromZero)
|
||||
if ($PSBoundParameters.ContainsKey('Scenes')) {
|
||||
$sceneStep = 0.1
|
||||
$sceneDirection = 0
|
||||
$pass = 0
|
||||
while ($true) {
|
||||
$pass++
|
||||
$changeTimes = @(Get-SceneChangeTime -Path $videoPath -Threshold $sceneLevel)
|
||||
$sceneList = @(Get-SceneList -Info $info -ChangeTimes $changeTimes -Template $Suffix)
|
||||
'Pass {0}: level {1} -> {2} scene(s)' -f $pass, $sceneLevel, $sceneList.Count | Out-Host
|
||||
$sceneList | Format-Table -AutoSize | Out-Host
|
||||
if ($sceneList.Count -eq $Scenes) { break }
|
||||
if ($sceneList.Count -lt $Scenes) {
|
||||
# Fewer scenes than requested: a lower level detects more scenes.
|
||||
if ($sceneDirection -eq 0) { $sceneDirection = -1 }
|
||||
elseif ($sceneDirection -eq 1) { $sceneDirection = -1; $sceneStep = $sceneStep / 2.0 }
|
||||
}
|
||||
else {
|
||||
# More scenes than requested: a higher level detects fewer scenes.
|
||||
if ($sceneDirection -eq 0) { $sceneDirection = 1 }
|
||||
elseif ($sceneDirection -eq -1) { $sceneDirection = 1; $sceneStep = $sceneStep / 2.0 }
|
||||
}
|
||||
if ($sceneStep -lt 0.01) {
|
||||
throw "The number of scenes -Scenes $Scenes could not be found - Search stopped at scene level $sceneLevel."
|
||||
}
|
||||
$sceneLevel = [Math]::Round($sceneLevel + ($sceneDirection * $sceneStep), 2, [MidpointRounding]::AwayFromZero)
|
||||
if ($sceneLevel -lt 0 -or $sceneLevel -gt 1) {
|
||||
throw "The number of scenes -Scenes $Scenes could not be found - Search stopped at scene level $sceneLevel, which is outside the range 0 to 1.0."
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$changeTimes = @(Get-SceneChangeTime -Path $videoPath -Threshold $sceneLevel)
|
||||
$sceneList = @(Get-SceneList -Info $info -ChangeTimes $changeTimes -Template $Suffix)
|
||||
$sceneList | Format-Table -AutoSize | Out-Host
|
||||
}
|
||||
'Scene level used: {0}' -f $sceneLevel | Out-Host
|
||||
if ($List) { return }
|
||||
|
||||
$confirm = [bool]$PSBoundParameters['Confirm']
|
||||
@@ -251,7 +299,7 @@ $pattern = Get-SceneFilePattern -VideoPath $videoPath -Template $Suffix
|
||||
Remove-ExistingScene -VideoPath $videoPath -Pattern $pattern -WhatIf:$WhatIfPreference -Confirm:$confirm
|
||||
|
||||
$failures = 0
|
||||
foreach ($clipScene in $scenes) {
|
||||
foreach ($clipScene in $sceneList) {
|
||||
$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
|
||||
|
||||
@@ -102,8 +102,9 @@ What it covers:
|
||||
|
||||
To use it, copy the whole `video-processing-scripts` folder directly into your agent's skills directory, keeping the folder name. Do not copy only `SKILL.md`: it links to the documentation pages by relative path, so those pages must stay in the same folder. For example:
|
||||
|
||||
- Devin: `.devin/skills/video-processing-scripts/`
|
||||
- Claude Code: `.claude/skills/video-processing-scripts/`
|
||||
- Claude Code: `"%USERPROFILE%\.claudeskills\video-processing-scripts\"`
|
||||
- Devin: `"%USERPROFILE%\.codeium\windsurf\skills\"`
|
||||
- VS Code: `"%USERPROFILE%\.copilot\skills\"`
|
||||
|
||||
The `video-processing-scripts` folder is available in both repositories:
|
||||
|
||||
|
||||
@@ -7,10 +7,11 @@ The clips are saved next to the source video, named from the source filename and
|
||||
## Parameters
|
||||
|
||||
- **`-Video`** (required): Path to the input video file. Can be absolute or relative to current directory. If the file does not exist, the script tries appending `.mp4`, `.webm` and `.mkv` in turn, so `P01` resolves to `P01.mp4` when that file exists.
|
||||
- **`-Scene`** (optional): Scene-change threshold between `0` and `1.0`. Lower values are more sensitive and produce more scenes. Default: `0.3`.
|
||||
- **`-Level`** (optional): Scene-change threshold between `0` and `1.0`, rounded to 2 decimals. Lower values are more sensitive and produce more scenes. Default: `0.3`.
|
||||
- **`-Scenes`** (optional): Target number of scenes, a positive integer. The level is searched automatically, starting from `-Level`, until exactly this many scenes are detected. Every attempt is displayed. If the number cannot be reached, the script stops with exit code `1` before any file is deleted or created. See [Automatic Level Search](#automatic-level-search).
|
||||
- **`-Suffix`** (optional): Suffix for the clip filenames. Must contain one printf-style number placeholder, which is replaced by the scene number starting at 1. Default: `-Scene-%02d`. See [Suffix Placeholders](#suffix-placeholders).
|
||||
- **`-List`** (optional): Only displays the scene list. No clip is created and no existing file is deleted.
|
||||
- **Extra parameters**: Any parameter accepted by `Create-Clip.ps1` and `Clone-Video.ps1` (e.g. `-Size`, `-FPS`, `-NoAudio`, `-Audio`, `-CRF`, `-Metadata`, `-Reverse`, `-LTX`) is forwarded to every clip. The parameters `-Video`, `-Frames`, `-Suffix` and `-Target` are managed by this script and must not be repeated.
|
||||
- **Extra parameters**: Any parameter accepted by `Create-Clip.ps1` and `Clone-Video.ps1` (e.g. `-Size`, `-FPS`, `-NoAudio`, `-Audio`, `-CRF`, `-Metadata`, `-Reverse`, `-LTX`) is forwarded to every clip. The parameters `-Video`, `-Frames`, `-Suffix`, `-Target`, `-Level` and `-Scenes` are managed by this script and must not be repeated. `-Level` and `-Scenes` are never forwarded to `Create-Clip.ps1`.
|
||||
|
||||
Refer to [Clone-Video.ps1](Clone-Video.md) for a full description of the encoding parameters.
|
||||
|
||||
@@ -21,7 +22,7 @@ Refer to [Clone-Video.ps1](Clone-Video.md) for a full description of the encodin
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Detection**: ffmpeg runs the `scene` score filter with the `-Scene` threshold and reports the timestamp of each detected change.
|
||||
1. **Detection**: ffmpeg runs the `scene` score filter with the `-Level` threshold and reports the timestamp of each detected change.
|
||||
2. **Frame conversion**: each timestamp becomes a 0-based frame index using the video's frame rate and start time. Indices at or before frame 0, or at or after the last frame, are ignored.
|
||||
3. **Scene list**: scene 1 starts at frame 0, and each detected change starts a new scene. Each scene ends one frame before the next change, and the last scene ends at the last frame. With no detected change, the whole video is one scene.
|
||||
4. **Scene list display**: the list is always printed, including with `-List`, `-WhatIf` and `-Confirm`.
|
||||
@@ -40,15 +41,37 @@ Index StartFrame EndFrame Frames StartTime EndTime TotalTime Suffix
|
||||
|
||||
`StartTime` and `EndTime` are in seconds, computed as frame index divided by frame rate. `EndTime` is the time of the last frame of the scene, so it is one frame before the next scene starts. `TotalTime` is the scene's duration in seconds: `Frames` divided by frame rate.
|
||||
|
||||
After the list, the script prints `Scene level used: <level>`, the threshold that produced the list.
|
||||
|
||||
## Scene Threshold
|
||||
|
||||
`-Scene` is passed to ffmpeg's `scene` score. The score measures how much a frame differs from the previous one, from `0` to `1`:
|
||||
`-Level` is passed to ffmpeg's `scene` score. The score measures how much a frame differs from the previous one, from `0` to `1`:
|
||||
|
||||
- `0.3` (default): a reasonable starting point for ordinary footage.
|
||||
- Lower values (e.g. `0.1`) detect gradual changes, such as slow camera moves, and produce more scenes.
|
||||
- Higher values (e.g. `0.6`) only detect hard cuts.
|
||||
- `1.0`: detects nothing, so the whole video becomes one scene.
|
||||
|
||||
## Automatic Level Search
|
||||
|
||||
With `-Scenes`, the script searches for a level that gives exactly the requested number of scenes:
|
||||
|
||||
1. It starts at `-Level` (rounded to 2 decimals) with a step of `0.1`.
|
||||
2. It detects the scenes and displays them, printed as `Pass N: level X -> M scene(s)` followed by the list.
|
||||
3. If the count matches `-Scenes`, the search stops.
|
||||
4. Too few scenes: the level moves down. Too many scenes: the level moves up. When the direction changes, the step is halved. Each new level is rounded to 2 decimals; the step is not rounded.
|
||||
5. The search stops with exit code `1` when the step falls below `0.01`, or when the level would leave the range `0` to `1.0`. Nothing is deleted or created in that case.
|
||||
|
||||
There is no limit on the number of passes. Each pass runs a full detection on the video, so long videos take longer. A count can be unreachable for a video: raising the level can skip a scene and jump past the target, so the search fails with exit code `1`.
|
||||
|
||||
The level used for the final list is shown on the last line, `Scene level used: <level>`.
|
||||
|
||||
**List exactly 4 scenes, starting at level 0.3:**
|
||||
|
||||
```powershell
|
||||
.\Create-Scenes.ps1 -Video P01.mp4 -Scenes 4 -List
|
||||
```
|
||||
|
||||
## Suffix Placeholders
|
||||
|
||||
The first printf-style placeholder in `-Suffix` is replaced by the scene number. The number is zero-padded to the width given in the placeholder:
|
||||
@@ -77,14 +100,14 @@ Matching is case-insensitive. Files that do not match are never touched. Existin
|
||||
| `P01.mp4` | `P01-Scene-01.mp4`, `P01-Scene-12.mp4` | `P01-Scene-01.skip.mp4`, `P01-Other.mp4` |
|
||||
| `P01.skip.mp4` | `P01-Scene-01.skip.mp4` | `P01-Scene-01.mp4` |
|
||||
|
||||
Because the deletion happens before the first clip is created, a second run with a higher `-Scene` value removes the extra clips from the first run.
|
||||
Because the deletion happens before the first clip is created, a second run with a higher `-Level` value removes the extra clips from the first run.
|
||||
|
||||
## Examples
|
||||
|
||||
**List the scenes without creating anything:**
|
||||
|
||||
```powershell
|
||||
.\Create-Scenes.ps1 -Video P01.mp4 -Scene 0.4 -List
|
||||
.\Create-Scenes.ps1 -Video P01.mp4 -Level 0.4 -List
|
||||
```
|
||||
|
||||
**Create one clip per scene:**
|
||||
@@ -97,7 +120,7 @@ Because the deletion happens before the first clip is created, a second run with
|
||||
**Scale and remove audio from every clip:**
|
||||
|
||||
```powershell
|
||||
.\Create-Scenes.ps1 -Video P01 -Scene 0.3 -Size 720p -NoAudio
|
||||
.\Create-Scenes.ps1 -Video P01 -Level 0.3 -Size 720p -NoAudio
|
||||
```
|
||||
|
||||
**Custom suffix:**
|
||||
@@ -136,5 +159,5 @@ This script supports `-WhatIf` and `-Confirm`. Both apply to the deletion of old
|
||||
- Frame indices are computed as `timestamp * frame rate`. This is exact for constant-frame-rate videos. With variable frame rate, a scene boundary can be off by one frame.
|
||||
- Clips are encoded by `Clone-Video.ps1`, so each clip keeps the source's resolution, frame rate and audio unless extra parameters change them.
|
||||
- Clips are written next to the source video, not to the current directory.
|
||||
- Scene detection is based on visual change only. A cut between two very similar shots may not be detected, and a fast camera move may be detected as a cut. Adjust `-Scene` to suit the footage.
|
||||
- Scene detection is based on visual change only. A cut between two very similar shots may not be detected, and a fast camera move may be detected as a cut. Adjust `-Level` to suit the footage, or use `-Scenes` to search for a level that gives a chosen number of scenes.
|
||||
- Each clip is created with `Create-Clip.ps1`, so a failed clip is reported as a warning and the other scenes are still created. At the end, the script reports how many scenes failed.
|
||||
@@ -156,7 +156,7 @@ pwsh -NoProfile -File "C:\path\to\Video Processing Scripts\Create-Clip.ps1" -Vid
|
||||
| [Create-Clip.ps1](Create-Clip.md) | Clip a frame range. The output name is the source name plus `-Suffix` (default `-Clip`). Forwards extra parameters to Clone-Video.ps1 | `-Video P01.mp4 -Frames "100-200"` |
|
||||
| [Create-FullSubtitle.ps1](Create-FullSubtitle.md) | Merge two SRT files into one, with timing taken from the video duration | `-Video v.mp4 -Subtitle1 a.srt -Subtitle2 b.srt -Output out.srt` |
|
||||
| [Create-PillarBox.ps1](Create-PillarBox.md) | Wrap a video in a 16:9 frame with side-bar effects (solid colours, or blurred backgrounds from the video). `-Vertical` gives a 9:16 frame for square sources | `-Video v.mp4 -Effect black` |
|
||||
| [Create-Scenes.ps1](Create-Scenes.md) | Detect scene changes and create one clip per scene. `-List` only shows the scenes. Forwards extra parameters to Create-Clip.ps1 and Clone-Video.ps1 | `-Video P01.mp4 -Scene 0.3 -List` |
|
||||
| [Create-Scenes.ps1](Create-Scenes.md) | Detect scene changes and create one clip per scene. `-List` only shows the scenes. `-Scenes` searches the level for a given scene count. Forwards extra parameters to Create-Clip.ps1 and Clone-Video.ps1 | `-Video P01.mp4 -Level 0.3 -List` |
|
||||
| [Create-ShortClip.ps1](Create-ShortClip.md) | Build a vertical short clip from an INI plan, with optional overlay, colour, smoothing and speed. `-Render` builds the video from existing frames | `-Video v.mp4 -Plan plan.ini` |
|
||||
| [Create-Turnaround.ps1](Create-Turnaround.md) | Arrange same-sized images into a row or grid turnaround sheet, with padding colour, quality and compression options | `-Source "C:\renders\CharacterA" -Target sheet.png` |
|
||||
| [Crop-Clips.ps1](Crop-Clips.md) | Batch crop LTX-2, Kling and Wan videos to standard sizes. `-Size` fits every video to a chosen size. Square videos become 1080x1080 | `-Source in_dir -Target out_dir` |
|
||||
@@ -181,9 +181,9 @@ pwsh -NoProfile -File .\Create-Clip.ps1 -Video P01.mp4 -Frames "100-200"
|
||||
**Split a video into scenes, with forwarded parameters** (list first, then create clips scaled to 720p without audio):
|
||||
|
||||
```powershell
|
||||
pwsh -NoProfile -File .\Create-Scenes.ps1 -Video P01.mp4 -Scene 0.3 -List
|
||||
pwsh -NoProfile -File .\Create-Scenes.ps1 -Video P01.mp4 -Scene 0.3 -Size 720p -NoAudio -WhatIf
|
||||
pwsh -NoProfile -File .\Create-Scenes.ps1 -Video P01.mp4 -Scene 0.3 -Size 720p -NoAudio
|
||||
pwsh -NoProfile -File .\Create-Scenes.ps1 -Video P01.mp4 -Level 0.3 -List
|
||||
pwsh -NoProfile -File .\Create-Scenes.ps1 -Video P01.mp4 -Scenes 4 -Size 720p -NoAudio -WhatIf
|
||||
pwsh -NoProfile -File .\Create-Scenes.ps1 -Video P01.mp4 -Scenes 4 -Size 720p -NoAudio
|
||||
```
|
||||
|
||||
**Extract the last frame to a named PNG:**
|
||||
|
||||
@@ -102,8 +102,9 @@ What it covers:
|
||||
|
||||
To use it, copy the whole `video-processing-scripts` folder directly into your agent's skills directory, keeping the folder name. Do not copy only `SKILL.md`: it links to the documentation pages by relative path, so those pages must stay in the same folder. For example:
|
||||
|
||||
- Devin: `.devin/skills/video-processing-scripts/`
|
||||
- Claude Code: `.claude/skills/video-processing-scripts/`
|
||||
- Claude Code: `"%USERPROFILE%\.claudeskills\video-processing-scripts\"`
|
||||
- Devin: `"%USERPROFILE%\.codeium\windsurf\skills\"`
|
||||
- VS Code: `"%USERPROFILE%\.copilot\skills\"`
|
||||
|
||||
The `video-processing-scripts` folder is available in both repositories:
|
||||
|
||||
|
||||
Reference in new issue
Block a user