From 4378e3fd995da9985892fc708819986bbda8c5f8 Mon Sep 17 00:00:00 2001 From: Claude Toupin Date: Mon, 2 Nov 2026 16:42:36 -0500 Subject: [PATCH] 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 --- Create-Scenes.ps1 | 76 ++++++++++++++++++----- README.md | 5 +- video-processing-scripts/Create-Scenes.md | 39 +++++++++--- video-processing-scripts/SKILL.md | 8 +-- video-processing-scripts/index.md | 5 +- 5 files changed, 103 insertions(+), 30 deletions(-) diff --git a/Create-Scenes.ps1 b/Create-Scenes.ps1 index b9c7e68..4a43ed7 100644 --- a/Create-Scenes.ps1 +++ b/Create-Scenes.ps1 @@ -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 diff --git a/README.md b/README.md index 92851e8..ac9afe0 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/video-processing-scripts/Create-Scenes.md b/video-processing-scripts/Create-Scenes.md index 932a1ea..cf7d98a 100644 --- a/video-processing-scripts/Create-Scenes.md +++ b/video-processing-scripts/Create-Scenes.md @@ -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: `, 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: `. + +**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. diff --git a/video-processing-scripts/SKILL.md b/video-processing-scripts/SKILL.md index 71f0495..d6a9fe6 100644 --- a/video-processing-scripts/SKILL.md +++ b/video-processing-scripts/SKILL.md @@ -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:** diff --git a/video-processing-scripts/index.md b/video-processing-scripts/index.md index 92851e8..ac9afe0 100644 --- a/video-processing-scripts/index.md +++ b/video-processing-scripts/index.md @@ -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: