Fix -Frames negative range calculation to extract last N frames correctly (off-by-one error), add validation to reject invalid '-%' and '-0' formats with exit code 1, change '%' alone to extract single last frame instead of range, remove duplicate last frame from PNG sequence concat list, update Get-LastFrame.ps1 and Get-Frame.ps1 to clamp out-of-range frame indices instead of erroring and improve error messages with inline stderr output, add -y flag and -- separator to ffprobe calls, replace deprecated
This commit is contained in:
1 parent
cf41fb0ce5
commit
1227c6e153
13 files changed
+100
-86
No files matched your search
@@ -45,8 +45,9 @@ The output container is chosen from the `-Target` extension (`.mp4`, `.webm`, or
|
||||
- **`-Frames`** (optional): 0-based frame range to clone. Mutually exclusive with `-StartFrame`, `-EndFrame`, and `-Sequence`. Supported formats:
|
||||
- `start-end`: Clone frames from `start` to `end` (e.g., `"100-500"`)
|
||||
- `positive_number`: Clone frames from 0 to that number (e.g., `"50"`)
|
||||
- `-N`: Clone last N frames (e.g., `"-10"`)
|
||||
- `%`: Represents the last frame index; can be used alone or in ranges (e.g., `"%"`, `"10-%"`, `"-%"`)
|
||||
- `-N`: Clone the last N frames (e.g., `"-10"` clones frames 90 to 99 of a 100-frame video; `"-1"` clones the last frame only)
|
||||
- `%`: The last frame index. Alone (`"%"`) it selects only the last frame. In a range (e.g., `"10-%"`) it is the end of the range
|
||||
- `-%` is not supported and is rejected with exit code `1`
|
||||
- `all`: Clone all frames (same as `"0-%"`)
|
||||
- **`-Sequence`** (optional): Path to PNG file for extracted frame (`.png` extension added if missing)
|
||||
- **`-Size`** (optional): Size of the output video. Uses the input video's size if not specified. Possible values: `320p`, `384p`, `480p`, `720p`, `900p`, `1080p`, `HD`, `1440p`, `2K`, `2160p`, `4K`, or custom size in format `width:height`. For standard named sizes the output **preserves the source aspect ratio**: the reference height is used as the short-side target and the other dimension is computed from the source AR — landscape/square sources use the reference as the output height (width computed), portrait sources use it as the output width (height computed). Custom sizes (`width:height`) are applied exactly as given. Forces a video re-encode, since scaling requires re-encoding.
|
||||
|
||||
@@ -10,8 +10,9 @@ Sources carrying a `.skip` marker keep that marker last: `P01.skip.mp4` becomes
|
||||
- **`-Frames`** (required): 0-based frame range to clip. Supported formats:
|
||||
- `start-end`: Clip frames from `start` to `end` (e.g., `"100-200"`)
|
||||
- `positive_number`: Clip frames from 0 to that number (e.g., `"50"`)
|
||||
- `-N`: Clip last N frames (e.g., `"-10"`)
|
||||
- `%`: Represents the last frame index; can be used alone or in ranges (e.g., `"%"`, `"10-%"`, `"-%"`)
|
||||
- `-N`: Clip the last N frames (e.g., `"-10"` clips frames 90 to 99 of a 100-frame video; `"-1"` clips the last frame only)
|
||||
- `%`: The last frame index. Alone (`"%"`) it selects only the last frame. In a range (e.g., `"10-%"`) it is the end of the range
|
||||
- `-%` is not supported and is rejected with exit code `1`
|
||||
- `all`: Clip all frames (same as `"0-%"`)
|
||||
- **`-Suffix`** (optional): Text inserted into the derived target name before the file extension. Defaults to `-Clip`, so `P01.mp4` becomes `P01-Clip.mp4`. Include any separator you want, such as the leading `-`. Must not be empty, because an empty suffix would produce a target with the same name as the source. For example, `-Suffix "-Clip-Shot-1"` turns `P01.mp4` into `P01-Clip-Shot-1.mp4`.
|
||||
- **Extra parameters**: Any additional parameters accepted by `Clone-Video.ps1` (e.g. `-Size`, `-FPS`, `-NoAudio`, `-Audio`, `-CRF`, `-Metadata`, `-Reverse`, `-LTX`) are forwarded directly. The parameters `-Video`, `-Frames`, `-Suffix` and `-Target` are handled by this script and must not be repeated.
|
||||
|
||||
@@ -31,14 +31,14 @@ Refer to [Clone-Video.ps1](Clone-Video.md) for a full description of the encodin
|
||||
Example scene list for a video with two hard cuts at frames 30 and 60:
|
||||
|
||||
```text
|
||||
Index StartFrame EndFrame Frames StartTime EndTime Suffix
|
||||
----- ---------- -------- ------ --------- ------- ------
|
||||
1 0 29 30 0.00 0.97 -Scene-01
|
||||
2 30 59 30 1.00 1.97 -Scene-02
|
||||
3 60 89 30 2.00 2.97 -Scene-03
|
||||
Index StartFrame EndFrame Frames StartTime EndTime TotalTime Suffix
|
||||
----- ---------- -------- ------ --------- ------- --------- ------
|
||||
1 0 29 30 0.00 0.97 1.00 -Scene-01
|
||||
2 30 59 30 1.00 1.97 1.00 -Scene-02
|
||||
3 60 89 30 2.00 2.97 1.00 -Scene-03
|
||||
```
|
||||
|
||||
`StartTime` and `EndTime` are in seconds, computed as frame index divided by frame rate.
|
||||
`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.
|
||||
|
||||
## Scene Threshold
|
||||
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ Extracts a specific frame of a video file to a PNG image by calling [Get-LastFra
|
||||
- **`-Frame`** (required): 0-based frame index to extract.
|
||||
- Positive values: Direct frame index (e.g., `0` = first frame, `100` = frame 100)
|
||||
- Negative values: Count from the end (e.g., `-1` = last frame, `-2` = second-to-last)
|
||||
- Values are automatically clamped to the valid range (0 to total frames − 1)
|
||||
- Values are automatically clamped to the valid range (0 to total frames − 1): a positive value past the last frame gives the last frame, and a negative value before the first frame gives frame 0
|
||||
|
||||
## Image Filename Derivation
|
||||
|
||||
|
||||
+13
-20
@@ -12,11 +12,11 @@ Extracts a specific range of frames from a video file to PNG images.
|
||||
|
||||
- **`start-end`**: Extract frames from start to end index (e.g., `100-200`)
|
||||
- **`positive_number`**: Extract frames from 0 to that number (e.g., `50` extracts frames 0-50)
|
||||
- **`-negative_number`**: Extract last N frames (e.g., `-10` extracts last 10 frames)
|
||||
- **`%`**: Represents the last frame index. Can be used alone or in ranges:
|
||||
- **`-negative_number`**: Extract the last N frames (e.g., `-10` extracts frames 90 to 99 of a 100-frame video; `-1` extracts the last frame only)
|
||||
- **`%`**: The last frame index. Alone (`%`) it extracts only the last frame. In a range it is the end of the range:
|
||||
- `%` - Extract only the last frame
|
||||
- `10-%` - Extract from frame 10 to the last frame
|
||||
- `-%` - Extract only the first frame (frame 0)
|
||||
- `-%` is not supported and is rejected with exit code `1`
|
||||
- **`all`**: Extract all frames (same as `0-%`)
|
||||
|
||||
## Examples
|
||||
@@ -24,63 +24,56 @@ Extracts a specific range of frames from a video file to PNG images.
|
||||
**Extract specific range:**
|
||||
|
||||
```powershell
|
||||
.\Get-Frames.ps1 -Video video.mp4 -Frames "100-200"
|
||||
.\Get-FramesRange.ps1 -Video video.mp4 -Frames "100-200"
|
||||
# Creates: Frames/video_0100.png, Frames/video_0101.png, ..., Frames/video_0200.png
|
||||
```
|
||||
|
||||
**Extract first 50 frames:**
|
||||
|
||||
```powershell
|
||||
.\Get-Frames.ps1 -Video video.mp4 -Frames "50"
|
||||
.\Get-FramesRange.ps1 -Video video.mp4 -Frames "50"
|
||||
# Creates: Frames/video_0000.png through Frames/video_0050.png
|
||||
```
|
||||
|
||||
**Extract last 10 frames:**
|
||||
|
||||
```powershell
|
||||
.\Get-Frames.ps1 -Video video.mp4 -Frames "-10"
|
||||
.\Get-FramesRange.ps1 -Video video.mp4 -Frames "-10"
|
||||
# If video has 500 frames, creates: Frames/video_0490.png through Frames/video_0499.png
|
||||
```
|
||||
|
||||
**Extract only the last frame:**
|
||||
|
||||
```powershell
|
||||
.\Get-Frames.ps1 -Video video.mp4 -Frames "%"
|
||||
.\Get-FramesRange.ps1 -Video video.mp4 -Frames "%"
|
||||
# If video has 500 frames, creates: Frames/video_0499.png
|
||||
```
|
||||
|
||||
**Extract from frame 10 to the last frame:**
|
||||
|
||||
```powershell
|
||||
.\Get-Frames.ps1 -Video video.mp4 -Frames "10-%"
|
||||
.\Get-FramesRange.ps1 -Video video.mp4 -Frames "10-%"
|
||||
# If video has 500 frames, creates: Frames/video_0010.png through Frames/video_0499.png
|
||||
```
|
||||
|
||||
**Extract only the first frame:**
|
||||
|
||||
```powershell
|
||||
.\Get-Frames.ps1 -Video video.mp4 -Frames "-%"
|
||||
# Creates: Frames/video_0000.png
|
||||
```
|
||||
|
||||
**Extract all frames:**
|
||||
|
||||
```powershell
|
||||
.\Get-Frames.ps1 -Video video.mp4 -Frames "all"
|
||||
.\Get-FramesRange.ps1 -Video video.mp4 -Frames "all"
|
||||
# If video has 500 frames, creates: Frames/video_0000.png through Frames/video_0499.png
|
||||
```
|
||||
|
||||
**Custom output pattern:**
|
||||
|
||||
```powershell
|
||||
.\Get-Frames.ps1 -Video video.mp4 -Frames "0-100" -Images "output_frame"
|
||||
.\Get-FramesRange.ps1 -Video video.mp4 -Frames "0-100" -Images "output_frame"
|
||||
# Creates: output_frame_0000.png through output_frame_0100.png
|
||||
```
|
||||
|
||||
**Full output path:**
|
||||
|
||||
```powershell
|
||||
.\Get-Frames.ps1 -Video video.mp4 -Frames "50-150" -Images "C:\Output\frame_%04d.png"
|
||||
.\Get-FramesRange.ps1 -Video video.mp4 -Frames "50-150" -Images "C:\Output\frame_%04d.png"
|
||||
```
|
||||
|
||||
**Note:** Frame numbers in output filenames reflect the actual video frame index (0-based).
|
||||
@@ -99,11 +92,11 @@ This script supports `-WhatIf` and `-Confirm` for safe execution:
|
||||
**Simulate without extracting:**
|
||||
|
||||
```powershell
|
||||
.\Get-Frames.ps1 -Video video.mp4 -Frames "100-200" -WhatIf
|
||||
.\Get-FramesRange.ps1 -Video video.mp4 -Frames "100-200" -WhatIf
|
||||
```
|
||||
|
||||
**Prompt before each frame:**
|
||||
|
||||
```powershell
|
||||
.\Get-Frames.ps1 -Video video.mp4 -Frames "0-10" -Confirm
|
||||
.\Get-FramesRange.ps1 -Video video.mp4 -Frames "0-10" -Confirm
|
||||
```
|
||||
@@ -9,7 +9,7 @@ Extracts a specific frame of a video file to a PNG image.
|
||||
- **`-Frame`** (optional): 0-based frame index to extract (default: -1)
|
||||
- Positive values: Direct frame index (e.g., 0 = first frame, 100 = frame 100)
|
||||
- Negative values: Count from the end (e.g., -1 = last frame, -2 = second-to-last)
|
||||
- Values are automatically clamped to valid range (0 to total frames - 1)
|
||||
- Values are automatically clamped to valid range (0 to total frames - 1): a positive value past the last frame gives the last frame, and a negative value before the first frame gives frame 0
|
||||
|
||||
## Filename Pattern
|
||||
|
||||
|
||||
+17
-10
@@ -11,7 +11,7 @@ The per-script pages sit in the same folder as this file. Load a page only when
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Platform**: Windows 11 only. The scripts are tested on Windows 11 and nowhere else. Linux and WSL are not supported. Run them with Windows PowerShell 7 (`pwsh.exe`).
|
||||
- **Platform**: Windows 11 only. The scripts are tested on Windows 11 and nowhere else. Linux and WSL are not supported. Run them with PowerShell 7 for Windows (`pwsh.exe`).
|
||||
- **PowerShell 7+** (`pwsh`). Required by every script.
|
||||
- **Core tools**: **ffmpeg** and **ffprobe** on `PATH`. Most scripts need both. [Stitch-InBetween.ps1](Stitch-InBetween.md) needs only ffmpeg. [Get-FramesCount.ps1](Get-FramesCount.md), [Get-VideoInfo.ps1](Get-VideoInfo.md) and [Create-FullSubtitle.ps1](Create-FullSubtitle.md) need only ffprobe.
|
||||
- **Optional tools**, needed only by specific scripts:
|
||||
@@ -49,7 +49,7 @@ If a core check fails, or a check needed by the current task fails, **stop**. Te
|
||||
|
||||
- Git: `git clone https://git.gurusushi.dyndns.org/guru/Video-Processing-Scripts.git` (repository `guru/Video-Processing-Scripts`).
|
||||
- SVN: `public-svn`, project `Video Processing Scripts`.
|
||||
- The script folder must be on `PATH` so scripts can be called from any directory.
|
||||
- The script folder should be on `PATH` so that `Get-Command` finds the scripts and the `.cmd` wrappers work. This does not make `pwsh -File <bare name>` work. See [How to Run the Scripts](#how-to-run-the-scripts).
|
||||
- `Update-Video-Processing-Scripts.cmd` updates a Git copy to the latest version. Run it only with the user's approval.
|
||||
|
||||
## How to Run the Scripts
|
||||
@@ -64,6 +64,12 @@ If a core check fails, or a check needed by the current task fails, **stop**. Te
|
||||
pwsh -NoProfile -File .\Create-Clip.ps1 -Video "My Video.mp4" -Frames "100-200"
|
||||
```
|
||||
|
||||
From any other folder, use the full script path and an absolute video path:
|
||||
|
||||
```powershell
|
||||
pwsh -NoProfile -File "C:\path\to\Video Processing Scripts\Create-Clip.ps1" -Video "D:\clips\P01.mp4" -Frames "100-200"
|
||||
```
|
||||
|
||||
## Conventions
|
||||
|
||||
- Frame indices are **0-based**: the first frame is `0`.
|
||||
@@ -73,18 +79,19 @@ pwsh -NoProfile -File .\Create-Clip.ps1 -Video "My Video.mp4" -Frames "100-200"
|
||||
| --- | --- |
|
||||
| `start-end` | Frames from `start` to `end`, e.g. `100-200` |
|
||||
| `N` | Frames 0 to `N`, not only frame `N` |
|
||||
| `-N` | The last `N` frames |
|
||||
| `%` | The last frame index, alone or in a range, e.g. `10-%` |
|
||||
| `-N` | The last `N` frames. `-1` is the last frame. |
|
||||
| `%` | The last frame index. Alone it selects only the last frame. In a range, e.g. `10-%`, it is the end of the range. |
|
||||
| `-%` | Not supported. Rejected with exit code `1`. |
|
||||
| `all` | All frames, same as `0-%` |
|
||||
|
||||
- **`-Frame` syntax** for [Get-Frame.ps1](Get-Frame.md) and [Get-LastFrame.ps1](Get-LastFrame.md): a single frame index. Negative values count from the end (`-1` is the last frame). Out-of-range values are clamped.
|
||||
- [Create-Scenes.ps1](Create-Scenes.md) takes no frame range. It finds scene boundaries itself.
|
||||
- **`-Frame` syntax** for [Get-Frame.ps1](Get-Frame.md) and [Get-LastFrame.ps1](Get-LastFrame.md): a single frame index. Negative values count from the end (`-1` is the last frame). [Get-Frame.ps1](Get-Frame.md) clamps values to the valid range: a positive value past the last frame gives the last frame, and a negative value before the first frame gives frame 0. [Get-LastFrame.ps1](Get-LastFrame.md) clamps the same way.
|
||||
- If the extension is omitted, `.mp4`, `.webm` and `.mkv` are tried in that order by [Clone-Video.ps1](Clone-Video.md), [Create-Clip.ps1](Create-Clip.md) and [Create-Scenes.ps1](Create-Scenes.md).
|
||||
- **`.skip` marker**: a file named `<name>.skip.<ext>` is excluded from batch processing, and the marker stays last in derived names.
|
||||
- [Create-Clip.ps1](Create-Clip.md) accepts `.mp4`, `.webm` and `.mkv`, case-insensitive. `P01.skip.mp4` gives `P01-Clip.skip.mp4`.
|
||||
- [Crop-Clips.ps1](Crop-Clips.md) processes only `.mp4` files and skips `*.skip.mp4` and `*.bak.mp4`.
|
||||
- [Create-Scenes.ps1](Create-Scenes.md) gives `P01-Scene-01.skip.mp4` for `P01.skip.mp4`.
|
||||
- Batch operations skip `.bak.mp4` backup files.
|
||||
- Scripts exit with `0` on success and `1` on failure. Check `$LASTEXITCODE` after each call.
|
||||
- On failure, scripts exit with `1`, print an error message to the error stream, and create no files. Check `$LASTEXITCODE` after each call.
|
||||
- **Forwarded parameters**: [Create-Clip.ps1](Create-Clip.md) and [Create-Scenes.ps1](Create-Scenes.md) accept the extra parameters of [Clone-Video.ps1](Clone-Video.md) and forward them, for example `-Size`, `-FPS`, `-NoAudio`, `-Audio`, `-CRF`, `-Metadata`, `-Reverse` and `-LTX`. Each reserves `-Video`, `-Frames`, `-Suffix` and `-Target` for itself, so do not repeat them in the extra parameters.
|
||||
|
||||
## Safety Rules
|
||||
@@ -102,7 +109,7 @@ pwsh -NoProfile -File .\Create-Clip.ps1 -Video "My Video.mp4" -Frames "100-200"
|
||||
| [Create-Clip.ps1](Create-Clip.md) | An existing derived clip is replaced without a prompt. The clip is written by Clone-Video.ps1, with the `.skip` marker kept last in the name. |
|
||||
| [Create-FullSubtitle.ps1](Create-FullSubtitle.md) | Overwrites `-Output` without a prompt. |
|
||||
| [Create-PillarBox.ps1](Create-PillarBox.md) | An existing `-Target` video is replaced without a prompt. If ffmpeg fails, the target is removed, including a file that existed before the run. |
|
||||
| [Create-Scenes.ps1](Create-Scenes.md) | Only when `-List` is not given. Deletes earlier clips of the same source in the source folder, then creates the new ones, which replace the old ones. |
|
||||
| [Create-Scenes.ps1](Create-Scenes.md) | Without `-List`, deletes the earlier scene clips of the same source in the source folder, then creates the new ones. `-List` creates and deletes nothing. |
|
||||
| [Create-ShortClip.ps1](Create-ShortClip.md) | Overwrites the output video. With the default `-Images` and without `-Render`, clears the `Frames` folder. With explicit `-Images`, deletes only matching frames. Removes its temporary folders, and the `Frames` folder unless `-Keep` is given. |
|
||||
| [Create-Turnaround.ps1](Create-Turnaround.md) | An existing `-Target` image is replaced without a prompt. The folder of `-Target` must exist. |
|
||||
| [Crop-Clips.ps1](Crop-Clips.md) | Works in place when `-Source` and `-Target` are omitted or equal. See the in-place rule below. |
|
||||
@@ -114,8 +121,8 @@ pwsh -NoProfile -File .\Create-Clip.ps1 -Video "My Video.mp4" -Frames "100-200"
|
||||
| [Get-VideoInfo.ps1](Get-VideoInfo.md) | Reads only. Creates and deletes nothing. |
|
||||
| [Stitch-InBetween.ps1](Stitch-InBetween.md) | Deletes an existing `-Video` before generating. Deletes everything inside an existing `-TempDir`. |
|
||||
|
||||
- **Crop-Clips in-place rule.** Crop-Clips works on the current folder when `-Source` and `-Target` are omitted, and it backs up each file it crops to `<name>.bak.mp4`, replacing any existing backup without a prompt. Skipped files are not backed up and keep their backup. A re-run with a different `-Size` crops the already cropped files again, so the backup then holds the previous cropped output and the true original is lost. So:
|
||||
- Always pass explicit `-Source` and `-Target`, and prefer a separate `-Target` folder.
|
||||
- **Crop-Clips in-place rule.** Crop-Clips works on the current folder when `-Source` and `-Target` are omitted or equal, and it backs up each file it crops to `<name>.bak.mp4`, replacing any existing backup without a prompt. Skipped files are not backed up and keep their backup. A re-run with a different `-Size` crops the already cropped files again, so the backup then holds the previous cropped output and the true original is lost. So:
|
||||
- Always pass explicit `-Source` and `-Target`, and prefer a separate `-Target` folder. Never pass the same folder for both.
|
||||
- Use in-place mode only when the user asks for it.
|
||||
- Warn the user before re-running in place with a different `-Size` on files that already have a `.bak.mp4`.
|
||||
- Never delete `.bak.mp4` files yourself unless the user asks.
|
||||
|
||||
Reference in new issue
Block a user