Add agent skill documentation section to README.md explaining SKILL.md file purpose, prerequisites coverage, safety rules, script catalog structure, and installation instructions for Devin and Claude Code agents, with repository location notes for both SVN and Git versions
This commit is contained in:
1 parent
c79451b373
commit
95e8d94298
19 files changed
+3413
No files matched your search
+217
@@ -0,0 +1,217 @@
|
|||||||
|
# Clone-Image.ps1
|
||||||
|
|
||||||
|
Clones (copies) an image file using ffmpeg with optional resizing, quality and compression settings. Reads PNG, JPEG, WEBP, BMP, AVIF and TIFF sources and writes to any of those formats depending on the `-Target` file extension.
|
||||||
|
|
||||||
|
## Synopsis
|
||||||
|
|
||||||
|
The output format is chosen from the `-Target` extension (`.png`, `.jpg`/`.jpeg`, `.webp`, `.bmp`, `.avif`, `.tif`/`.tiff`; defaults to `.png` when no recognised extension is given). Each format has its own encoder profile — the script picks the appropriate ffmpeg encoder and applies any quality/compression settings. See [Output formats](#output-formats) for the full matrix.
|
||||||
|
|
||||||
|
When `-Size` is provided, the source image is resized using a **cover-then-crop** strategy:
|
||||||
|
|
||||||
|
1. The image is scaled (preserving its original pixel aspect ratio) so it fully covers the requested width *and* height — one dimension matches exactly, the other overflows.
|
||||||
|
2. The overflowing dimension is then centre-cropped so the final image is exactly the requested size.
|
||||||
|
|
||||||
|
The source aspect ratio is preserved at all times (no warping/stretching).
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
- **`-Source`** (required): Path to input image file. Accepted formats: `.png`, `.jpg`, `.jpeg`, `.webp`, `.bmp`, `.avif`, `.tif`, `.tiff`. If the path has no extension, the script tries each supported extension in turn.
|
||||||
|
- **`-Target`** (required): Path to output image file. The extension picks the output format (PNG, JPEG, WEBP, BMP, AVIF, or TIFF). If the extension is missing or unrecognised, `.png` is appended and the PNG profile is used.
|
||||||
|
- **`-Size`** (optional): Size of the output image. Uses the source image's size if not specified. Possible values: `480p`, `720p`, `1080p`, `HD`, `1440p`, `2K`, `2160p`, `4K`, or custom size in format `width:height`. When specified, the source is **cover-scaled** (aspect ratio preserved) so it fully covers the requested dimensions, then **centre-cropped** to the exact target size. No image warping.
|
||||||
|
- **`-Quality`** (optional): Quality factor for lossy output formats (JPEG, WEBP, AVIF). **Lower values mean better quality** (consistent across all three formats). Ignored for PNG, BMP and TIFF (lossless).
|
||||||
|
- **JPEG**: passed to ffmpeg's `-q:v` (typical range `1-31`, lower = better).
|
||||||
|
- **WEBP**: inverted internally and passed to libwebp's `-quality` (`0-100`, higher = better), so the "lower = better" semantic is preserved from the caller's perspective.
|
||||||
|
- **AVIF**: passed to libaom-av1's `-crf` (`0-63`, lower = better).
|
||||||
|
- **`-Compression`** (optional): Compression level for PNG and WEBP outputs.
|
||||||
|
- **PNG**: libpng's `-compression_level` (`0-9`, higher = smaller files / slower encoding).
|
||||||
|
- **WEBP**: libwebp's `-compression_level` (`0-6`, higher = smaller files / slower encoding).
|
||||||
|
- Ignored for JPEG, AVIF, BMP and TIFF.
|
||||||
|
- **`-Metadata`**: Copy available metadata from source image file to target image file.
|
||||||
|
- **`-Upscale`** (optional): Upscale factor (`2`, `3` or `4`) applied to the source image with `upscayl-bin.exe` **before** ffmpeg runs. The upscaled image is written to a Windows temp PNG (under `%TEMP%`) and used as the ffmpeg input, so any `-Size` / `-LTX` cover-scale-then-crop is applied to the already-upscaled image. The temp PNG is deleted automatically when the script finishes. Requires `upscayl-bin.exe` in PATH and the `UPSCAYL_HOME` and `NCNN_GPU_ID` environment variables (see [index](index.md)). Omit (or pass `0`) to disable upscaling.
|
||||||
|
- **`-Model`** (optional): Upscayl model name to use when `-Upscale` is specified. Defaults to `4xNomos8kSC`. The model files (`<Model>.bin` and `<Model>.param`) must exist under `%UPSCAYL_HOME%\models` (see the [Upscalers installation guide](Upscalers.md) for installing additional models). Ignored when `-Upscale` is not specified.
|
||||||
|
- **`-Keep`** (optional): Path at which to save a copy of the upscayl-bin output (i.e. the upscaled image, **before** ffmpeg's cover-scale-then-crop). The file extension picks the format (`.png`, `.jpg`/`.jpeg`, `.webp`, `.bmp`, `.avif`, `.tif`/`.tiff`); if the extension is missing or unrecognised, `.png` is appended. When the requested format is `.png` (the upscayl native output) the temp file is simply copied; otherwise it is converted with ffmpeg using the same per-format encoders as `-Target` (with default quality settings). Only valid when `-Upscale` is specified — it is ignored (with a warning) otherwise.
|
||||||
|
- **`-LTX`**: Override the dimensions chosen by `-Size` with the closest LTX-2 (Wan2GP by DeepBeepMeep) canonical resolution for the source's aspect ratio. Only valid when `-Size` is one of `HD`, `1080p`, `720p` or `480p`. The script probes the source's width/height, computes its aspect ratio, and picks the bucket whose ratio is numerically closest. Cover-scale-then-centre-crop still applies, so the source is fitted to the picked LTX dimensions without warping. The buckets per `-Size` are:
|
||||||
|
- **`HD` / `1080p`**: `1920x1088` (16:9), `1088x1920` (9:16), `1664x960` (21:9), `960x1664` (9:21).
|
||||||
|
- **`720p`**: `1280x704` (16:9), `704x1280` (9:16), `1088x576` (21:9), `576x1088` (9:21).
|
||||||
|
- **`480p`**: `832x448` (16:9), `448x832` (9:16), `896x512` (4:3), `512x896` (3:4).
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
**Clone entire image (same size, same format):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Image.ps1 -Source photo.png -Target output.png
|
||||||
|
```
|
||||||
|
|
||||||
|
**Convert PNG to JPEG with default quality:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Image.ps1 -Source photo.png -Target photo.jpg
|
||||||
|
```
|
||||||
|
|
||||||
|
**Convert with custom JPEG quality (lower = better):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Image.ps1 -Source photo.png -Target photo.jpg -Quality 3
|
||||||
|
# Uses ffmpeg -q:v 3
|
||||||
|
```
|
||||||
|
|
||||||
|
**Convert to WEBP at high quality:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Image.ps1 -Source photo.png -Target photo.webp -Quality 10
|
||||||
|
# Internally maps to libwebp -quality 90 (100 - 10)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Convert to AVIF at near-visually-lossless:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Image.ps1 -Source photo.png -Target photo.avif -Quality 18
|
||||||
|
# Uses libaom-av1 -crf 18 -still-picture 1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Upscale 4x with the default model, then crop to 4K:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Image.ps1 -Source small.jpg -Target big_4k.png -Upscale 4 -Size 4K
|
||||||
|
# small.jpg is upscaled x4 with 4xNomos8kSC into a temp PNG;
|
||||||
|
# ffmpeg then cover-scales the upscaled image and centre-crops to 3840x2160.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Upscale 2x with a custom model:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Image.ps1 -Source photo.png -Target photo_2x.png -Upscale 2 -Model 4xUltraSharp
|
||||||
|
# Requires 4xUltraSharp.bin and 4xUltraSharp.param under %UPSCAYL_HOME%\models.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Upscale 4x to a JPEG with custom quality:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Image.ps1 -Source thumb.jpg -Target thumb_hi.jpg -Upscale 4 -Quality 3
|
||||||
|
```
|
||||||
|
|
||||||
|
**Upscale 4x and also keep the full-resolution upscaled PNG:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Image.ps1 -Source small.jpg -Target small_4k.png -Upscale 4 -Size 4K -Keep small_upscaled.png
|
||||||
|
# small_upscaled.png is the raw upscayl output (no crop); small_4k.png is cover-scaled+cropped to 3840x2160.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Upscale and keep the upscaled image as JPEG (format conversion via ffmpeg):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Image.ps1 -Source photo.png -Target photo_2x.png -Upscale 2 -Keep photo_upscaled.jpg
|
||||||
|
# upscayl writes a PNG; ffmpeg converts that PNG to photo_upscaled.jpg before the main clone step.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Resize using a named preset (cover-scale then crop):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Image.ps1 -Source photo.png -Target photo_720.png -Size 720p
|
||||||
|
# Scales the source so it fully covers 1280x720, then centre-crops to exactly 1280x720.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pick the closest LTX-2 720p bucket from a 16:9 source:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Image.ps1 -Source landscape.png -Target landscape_ltx720.png -Size 720p -LTX
|
||||||
|
# A 1920x1080 (16:9) source is fitted to 1280x704.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pick the closest LTX-2 1080p bucket from a 9:16 phone shot:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Image.ps1 -Source portrait.jpg -Target portrait_ltx1080.png -Size 1080p -LTX
|
||||||
|
# A 1080x1920 (9:16) source is fitted to 1088x1920.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pick the closest LTX-2 480p bucket from a 4:3 source:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Image.ps1 -Source legacy.jpg -Target legacy_ltx480.png -Size 480p -LTX
|
||||||
|
# A 1024x768 (4:3) source is fitted to 896x512.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Resize with a custom resolution:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Image.ps1 -Source photo.png -Target square.jpg -Size 1024:1024
|
||||||
|
# Scales so the image fully covers 1024x1024, then centre-crops to a square.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Maximum PNG compression:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Image.ps1 -Source photo.png -Target small.png -Compression 9
|
||||||
|
```
|
||||||
|
|
||||||
|
**WEBP with explicit quality and compression effort:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Image.ps1 -Source photo.png -Target photo.webp -Quality 20 -Compression 6
|
||||||
|
# libwebp -quality 80 -compression_level 6
|
||||||
|
```
|
||||||
|
|
||||||
|
**Preserve metadata when converting formats:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Image.ps1 -Source photo.jpg -Target photo.webp -Metadata
|
||||||
|
# Copies EXIF/XMP/etc. that ffmpeg can transport into the WEBP container.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Using absolute paths:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Image.ps1 -Source "C:\Photos\input.png" -Target "D:\Output\result.jpg"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output formats
|
||||||
|
|
||||||
|
The output format is selected from the `-Target` file extension. Each format has its own ffmpeg encoder and the parameters that apply to it.
|
||||||
|
|
||||||
|
| Format | Target extension(s) | ffmpeg encoder | Lossy? | `-Quality` applies | `-Compression` applies |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| PNG | `.png` (default) | `png` | No | No | Yes (`0-9`, higher = smaller) |
|
||||||
|
| JPEG | `.jpg`, `.jpeg` | `mjpeg` (yuvj420p) | Yes | Yes (`1-31`, lower = better) | No |
|
||||||
|
| WEBP | `.webp` | `libwebp` | Yes | Yes (inverted: `0-100`, lower = better) | Yes (`0-6`, higher = smaller) |
|
||||||
|
| AVIF | `.avif` | `libaom-av1` (still-picture) | Yes | Yes (`0-63`, lower = better) | No |
|
||||||
|
| BMP | `.bmp` | `bmp` | No | No | No |
|
||||||
|
| TIFF | `.tif`, `.tiff` | `tiff` | No | No | No |
|
||||||
|
|
||||||
|
### General rules
|
||||||
|
|
||||||
|
- The output format is determined entirely by the `-Target` extension. If `-Target` has no extension (or an unrecognised one), `.png` is appended and the PNG profile is used.
|
||||||
|
- Resizing always uses the **cover-scale then centre-crop** strategy. The source pixel aspect ratio is preserved (no warping). When `-Size` is omitted, the source dimensions are kept as-is.
|
||||||
|
- `-Quality` and `-Compression` are interpreted in a format-agnostic way from the caller's perspective: **lower `-Quality` always means better quality**, and **higher `-Compression` always means smaller files**. Per-format ffmpeg mappings (including the libwebp `-quality` inversion) happen inside the script.
|
||||||
|
- Defaults when `-Quality`/`-Compression` are omitted: JPEG uses `-q:v 2`, WEBP uses `-quality 90`, AVIF uses `-crf 25`. PNG and lossless formats are encoded with ffmpeg's default settings.
|
||||||
|
- AVIF output uses `libaom-av1` with `-still-picture 1` and `-cpu-used 4` for a reasonable speed/quality tradeoff. Your ffmpeg build must include `libaom-av1` for `.avif` output to work.
|
||||||
|
- `-Metadata` toggles ffmpeg's `-map_metadata 0` (copy) vs `-map_metadata -1` (strip). Which metadata actually survives the conversion depends on what the source and target formats can carry.
|
||||||
|
- `-LTX` only takes effect when `-Size` is `HD`, `1080p`, `720p` or `480p`. Combining it with any other `-Size` value (or omitting `-Size`) is rejected with an explicit error so the user is never silently downgraded to the regular preset.
|
||||||
|
- `-Upscale` runs `upscayl-bin.exe` **before** ffmpeg. The script validates that `upscayl-bin.exe` is on PATH, that `UPSCAYL_HOME` is set and exists, that the `models` subfolder and the requested `<Model>.bin` / `<Model>.param` files are present, and that `NCNN_GPU_ID` is set to a valid integer (`-1` for CPU, `0+` for GPU). The intermediate upscaled PNG is created under `%TEMP%` and deleted after the run (including on ffmpeg failure).
|
||||||
|
- `-Keep` saves the raw upscayl output (no cropping/scaling). When the `-Keep` extension is `.png`, the temp file is copied directly (no re-encode); for any other supported extension a separate ffmpeg invocation converts the PNG to the requested format with default quality settings (JPEG `-q:v 2`, WEBP `-quality 90`, AVIF `-crf 25`). `-Keep` is ignored with a warning if `-Upscale` is not specified.
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
- **AVIF encoding is slow.** `libaom-av1` is significantly slower than the other encoders even with `-cpu-used 4`. WebP at high `-Compression` is also slower than PNG or JPEG.
|
||||||
|
- **JPEG output is always 4:2:0 chroma** (`yuvj420p`). If you need 4:4:4 JPEG or 16-bit output, edit the encoder args directly.
|
||||||
|
- **TIFF and BMP** are stored uncompressed (TIFF can be configured for LZW/Deflate via additional ffmpeg options if needed).
|
||||||
|
|
||||||
|
## Safe Execution
|
||||||
|
|
||||||
|
This script supports `-WhatIf` and `-Confirm` for safe execution:
|
||||||
|
|
||||||
|
- **`-WhatIf`**: Simulate the operation without actually creating files. Shows what would be done.
|
||||||
|
- **`-Confirm`**: Prompt for confirmation before each state-changing operation.
|
||||||
|
|
||||||
|
**Simulate without making changes:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Image.ps1 -Source photo.png -Target output.jpg -WhatIf
|
||||||
|
```
|
||||||
|
|
||||||
|
**Prompt before creating files:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Image.ps1 -Source photo.png -Target output.avif -Confirm
|
||||||
|
```
|
||||||
+476
@@ -0,0 +1,476 @@
|
|||||||
|
# Clone-Video.ps1
|
||||||
|
|
||||||
|
Clones (copies) a video file using ffmpeg with optional frame range selection and sequence extraction. Reads MP4 (H.264), WebM and Matroska (MKV) sources — including YouTube/Google DASH downloads encoded as VP9 + Opus/AAC — and writes MP4, WebM, or MKV output depending on the `-Target` file extension. Also builds a video from a directory of sequentially-named PNG files (e.g. `frame_0000.png`) when `-Source` points to a folder, or from a single still image (`.png`, `.jpg`, `.jpeg`, `.webp`, `.bmp`, `.tiff`, `.tif`, `.gif`) for the duration of a provided audio file when `-Source` is an image and `-Audio` is specified.
|
||||||
|
|
||||||
|
## Synopsis
|
||||||
|
|
||||||
|
When `Source` is a **directory** containing PNG files with sequence numbers:
|
||||||
|
|
||||||
|
- PNG files are sorted alphabetically (ascending) to form the frame list
|
||||||
|
- `-StartFrame` / `-EndFrame` filter the list by 0-based index before encoding
|
||||||
|
- If `-Reverse` is set, the list is sorted descending **after** any frame range filtering
|
||||||
|
- If `-Size` is not specified, the dimensions of the first PNG are used
|
||||||
|
- If `-FPS` is not specified, 24 fps is used
|
||||||
|
- If `-CRF` is not specified, CRF 8 is used
|
||||||
|
- If `-Audio` is not specified, the output has no audio
|
||||||
|
- `-LTX` applies the same closest-bucket logic as for video sources
|
||||||
|
- `-Crop` applies the same [crop rules](#crop-rules) as for video sources
|
||||||
|
- `-Sequence` and `-Metadata` are **not** allowed in this mode and produce an error
|
||||||
|
|
||||||
|
When `Source` is a **supported image file** (`.png`, `.jpg`, `.jpeg`, `.webp`, `.bmp`, `.tiff`, `.tif`, `.gif`) and `-Audio` is provided:
|
||||||
|
|
||||||
|
- The image is looped as every frame for the duration of the audio file
|
||||||
|
- `-NoAudio` is **not** allowed in this mode
|
||||||
|
- If `-Size` is not specified, the source image dimensions are used
|
||||||
|
- If `-FPS` is not specified, 24 fps is used
|
||||||
|
- If `-CRF` is not specified, CRF 8 is used
|
||||||
|
- `-Size`, `-FPS`, `-CRF`, `-LTX` and `-Crop` are honoured
|
||||||
|
- `-StartFrame`, `-EndFrame`, `-Frames`, `-Sequence`, `-Metadata` and `-Reverse` are ignored
|
||||||
|
|
||||||
|
When `-Frames` is provided, it defines a frame range using a flexible format and is **mutually exclusive** with `-StartFrame`, `-EndFrame` and `-Sequence`.
|
||||||
|
|
||||||
|
When `Sequence` is provided (video source mode):
|
||||||
|
|
||||||
|
- If `EndFrame` is `-1`: Extracts the last frame to PNG, then clones video excluding the last frame
|
||||||
|
- If `EndFrame` is not `-1`: Extracts the frame at `EndFrame` to PNG, then clones video from `StartFrame` to `EndFrame-1`
|
||||||
|
|
||||||
|
The output container is chosen from the `-Target` extension (`.mp4`, `.webm`, or `.mkv`; defaults to `.mp4` when no recognised extension is given). Each container has its own codec profile — the script stream-copies streams that are already compatible and transcodes anything else to the container's preferred codecs. See [Output containers](#output-containers) for the full matrix.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
- **`-Source`** (required): Path to an input video file **or** a directory containing PNG files with sequence numbers (e.g. `frame_0000.png`) **or** a supported image file. For video files — accepted containers: `.mp4`, `.webm`, `.mkv`; if the path has no extension the script tries `.mp4`, `.webm`, then `.mkv` in turn. For PNG sequence directories — see [PNG Image Sequence Mode](#png-image-sequence-mode). For supported image files — see [Image-to-Video Mode](#image-to-video-mode).
|
||||||
|
- **`-Target`** (required): Path to output video file. The extension picks the output container: `.mp4` (H.264/AAC), `.webm` (VP9/Opus), or `.mkv` (H.264/AAC, permissive stream-copy). If the extension is missing or unrecognised, `.mp4` is appended and the MP4 profile is used.
|
||||||
|
- **`-StartFrame`** (optional): 0-based frame index to start cloning from (default: 0). Mutually exclusive with `-Frames`.
|
||||||
|
- **`-EndFrame`** (optional): 0-based frame index to end cloning at, use -1 for end (default: -1). Mutually exclusive with `-Frames`.
|
||||||
|
- **`-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-%"`, `"-%"`)
|
||||||
|
- `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.
|
||||||
|
- **`-FPS`** (optional): Frames per second for the output video. Uses the input video's FPS if not specified. Forces a video re-encode, since changing frame rate requires re-encoding.
|
||||||
|
- **`-NoAudio`**: Exclude audio from cloned video. Forces a video re-encode, since removing the audio stream requires re-muxing the output file.
|
||||||
|
- **`-Audio`** (optional): Path to an alternate audio file to use for the target video. When specified, this audio replaces the source video's audio. Cannot be used with `-NoAudio`. When `-Source` is a supported image file, `-Audio` determines the output video duration.
|
||||||
|
- **`-CRF`** (optional): 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
|
||||||
|
- **`-Metadata`**: Copy available JSON metadata from source video file to target video file
|
||||||
|
- **`-Reverse`**: Re-encode the video so it plays backward (last selected frame to first). Honours all other parameters (frame range, `-Size`, `-FPS`, `-CRF`, target container, etc.). Audio is **not** reversed — it is processed exactly as without `-Reverse` (trimmed/replaced/copied as applicable) and plays forward alongside the reversed video. Forces a video re-encode, since stream-copy is incompatible with the `reverse` filter.
|
||||||
|
- **`-LTX`**: Override the dimensions chosen by `-Size` with the closest LTX-2 (Wan2GP by DeepBeepMeep) canonical resolution for the source's aspect ratio. Only valid when `-Size` is one of `HD`, `1080p`, `720p` or `480p`. The script probes the source's width/height, computes its aspect ratio, and picks the bucket whose ratio is numerically closest. Forces a video re-encode (same as `-Size`). The buckets per `-Size` are:
|
||||||
|
- **`HD` / `1080p`**: `1920x1088` (16:9), `1088x1920` (9:16), `1664x960` (21:9), `960x1664` (9:21).
|
||||||
|
- **`720p`**: `1280x704` (16:9), `704x1280` (9:16), `1088x576` (21:9), `576x1088` (9:21).
|
||||||
|
- **`480p`**: `832x448` (16:9), `448x832` (9:16), `896x512` (4:3), `512x896` (3:4).
|
||||||
|
- **`-Crop`**: Obtain the dimensions selected by `-Size` (including `-LTX` buckets) by **center-cropping** the source instead of resizing it, so the original aspect ratio of the picture content is kept (no stretching or scaling). Requires `-Size`. Without `-Crop` the source is resized (default behaviour). Honoured in all modes (video, PNG sequence and image-to-video). Forces a video re-encode (same as `-Size`). See [Crop rules](#crop-rules).
|
||||||
|
|
||||||
|
## Crop rules
|
||||||
|
|
||||||
|
When `-Crop` is present, the requested output size is first computed exactly as without `-Crop` (named presets apply the short-side AR rule, custom `width:height` is used as given, `-LTX` picks its bucket). The source is then cropped around its center instead of being scaled:
|
||||||
|
|
||||||
|
- A dimension is cropped only when the source is **bigger** than the requested size in that dimension.
|
||||||
|
- A dimension where the source is equal or smaller is left untouched — no upscaling or padding is applied, so the output can be smaller than the requested size.
|
||||||
|
- Cropped dimensions are rounded down to an even number (required by `yuv420p`).
|
||||||
|
|
||||||
|
| Source | `-Size` | Without `-Crop` (resize) | With `-Crop` |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `1080x1080` | `960:1080` | `960x1080` (squeezed horizontally) | `960x1080` (cropped horizontally) |
|
||||||
|
| `1080x1080` | `720:720` | `720x720` (scaled down) | `720x720` (cropped horizontally and vertically) |
|
||||||
|
| `1080x1080` | `1920:720` | `1920x720` (stretched) | `1080x720` (cropped vertically only) |
|
||||||
|
|
||||||
|
## Image-to-Video Mode
|
||||||
|
|
||||||
|
When `-Source` is a supported image file and `-Audio` is provided, the script builds a video of the static image lasting for the duration of the audio file, bypassing all video-probing logic.
|
||||||
|
|
||||||
|
### Supported image formats
|
||||||
|
|
||||||
|
`.png`, `.jpg`, `.jpeg`, `.webp`, `.bmp`, `.tiff`, `.tif`, `.gif`
|
||||||
|
|
||||||
|
### Defaults vs. overrides
|
||||||
|
|
||||||
|
| Parameter | Default when omitted | Can be overridden? |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `-FPS` | 24 | Yes — any positive value |
|
||||||
|
| `-CRF` | 8 | Yes — any value 0-51 |
|
||||||
|
| `-Size` | Source image dimensions (even-padded) | Yes — any named or custom size |
|
||||||
|
|
||||||
|
### Dimension rules
|
||||||
|
|
||||||
|
- If `-Size` is a **named preset** (`720p`, `1080p`, etc.) without `-LTX`, the short-side AR rule from video mode applies using the source image's dimensions.
|
||||||
|
- If `-Size` is a **custom** `width:height`, it is applied exactly.
|
||||||
|
- If `-Size` is omitted, the source image's width/height are used directly (padded to an even number if needed).
|
||||||
|
- `-LTX` picks the closest LTX-2 bucket from the source image's aspect ratio. Only valid with `-Size` `HD`, `1080p`, `720p` or `480p`.
|
||||||
|
- `-Crop` center-crops the source image to the computed size instead of resizing it (see [Crop rules](#crop-rules)).
|
||||||
|
|
||||||
|
### Restrictions
|
||||||
|
|
||||||
|
- `-NoAudio` is not allowed; audio is required in this mode.
|
||||||
|
- `-StartFrame`, `-EndFrame`, `-Frames`, `-Sequence`, `-Metadata` and `-Reverse` are ignored.
|
||||||
|
|
||||||
|
### Output container
|
||||||
|
|
||||||
|
The output container is determined by the `-Target` extension (`.mp4`, `.webm`, `.mkv`), using the same codec profiles as for video sources. For `libx264` targets (MP4/MKV), `-tune stillimage` is applied to optimise encoding for static content.
|
||||||
|
|
||||||
|
## PNG Image Sequence Mode
|
||||||
|
|
||||||
|
When `-Source` is a directory the script builds a video from the PNG files found inside, bypassing all video-probing logic. The behaviour is fully driven by the parameters below.
|
||||||
|
|
||||||
|
### Frame selection
|
||||||
|
|
||||||
|
`-StartFrame` and `-EndFrame` are **0-based indices** into the alphabetically-sorted PNG list (not time-codes). The default range covers all PNG files. `-Frames` can be used instead of `-StartFrame`/`-EndFrame` to specify the range using the flexible format described in the [Parameters](#parameters) section.
|
||||||
|
|
||||||
|
### Ordering
|
||||||
|
|
||||||
|
The list is always built in ascending alphabetical order first. `-Reverse` then reverses it **after** the frame range has been applied, so the first frame in the output is `list[EndFrame]` and the last is `list[StartFrame]`.
|
||||||
|
|
||||||
|
### Defaults vs. overrides
|
||||||
|
|
||||||
|
| Parameter | Default when omitted | Can be overridden? |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `-FPS` | 24 | Yes — any positive value |
|
||||||
|
| `-CRF` | 8 | Yes — any value 0-51 |
|
||||||
|
| `-Size` | First PNG dimensions (even-padded) | Yes — any named or custom size |
|
||||||
|
| Audio | No audio (`-an`) | Yes — provide `-Audio` |
|
||||||
|
|
||||||
|
### Dimension rules
|
||||||
|
|
||||||
|
- If `-Size` is a **named preset** (`720p`, `1080p`, etc.) without `-LTX`, the short-side AR rule from video mode applies using the first PNG's dimensions.
|
||||||
|
- If `-Size` is a **custom** `width:height`, it is applied exactly (as with video mode).
|
||||||
|
- If `-Size` is omitted, the first PNG's width/height are used directly (padded to an even number if needed).
|
||||||
|
- `-LTX` picks the closest LTX-2 bucket from the first PNG's aspect ratio. Only valid with `-Size` `HD`, `1080p`, `720p` or `480p`.
|
||||||
|
- `-Crop` center-crops the PNG frames to the computed size instead of resizing them (see [Crop rules](#crop-rules)).
|
||||||
|
|
||||||
|
### Unsupported parameters
|
||||||
|
|
||||||
|
`-Sequence` and `-Metadata` are not valid in PNG sequence mode. Specifying either produces a terminating error before any file I/O occurs.
|
||||||
|
|
||||||
|
### Output container
|
||||||
|
|
||||||
|
The output container is still determined by the `-Target` extension (`.mp4`, `.webm`, `.mkv`), using the same codec profiles as for video sources.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
**Clone entire video:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source video.mp4 -Target output.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clone specific frame range:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source video.mp4 -Target clip.mp4 -StartFrame 100 -EndFrame 500
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clone frame range using `-Frames`:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source video.mp4 -Target clip.mp4 -Frames "100-500"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clone from frame 0 to frame 50:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source video.mp4 -Target clip.mp4 -Frames "50"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clone last 10 frames:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source video.mp4 -Target clip.mp4 -Frames "-10"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clone from frame 10 to last frame:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source video.mp4 -Target clip.mp4 -Frames "10-%"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Extract last frame and clone without it:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source video.mp4 -Target output.mp4 -Sequence last_frame.png
|
||||||
|
# Extracts last frame to last_frame.png
|
||||||
|
# Clones all frames except the last one
|
||||||
|
```
|
||||||
|
|
||||||
|
**Extract specific frame and clone up to it:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source video.mp4 -Target clip.mp4 -EndFrame 100 -Sequence frame100.png
|
||||||
|
# Extracts frame 100 to frame100.png
|
||||||
|
# Clones frames 0-99 (excluding frame 100)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clone with resized output:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source video.mp4 -Target output.mp4 -Size 720p
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clone with custom resolution:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source video.mp4 -Target output.mp4 -Size 1280:720
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clone with cropped output (no resizing):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source square.mp4 -Target output.mp4 -Size 960:1080 -Crop
|
||||||
|
# A 1080x1080 source is center-cropped horizontally to 960x1080.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clone with output cropped on both dimensions:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source square.mp4 -Target output.mp4 -Size 720:720 -Crop
|
||||||
|
# A 1080x1080 source is center-cropped horizontally and vertically to 720x720.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clone with different FPS:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source video.mp4 -Target output.mp4 -FPS 24
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clone with resized output and different FPS:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source video.mp4 -Target output.mp4 -Size 1080p -FPS 30
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clone without audio:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source video.mp4 -Target silent.mp4 -NoAudio
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clone with alternate audio:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source video.mp4 -Target output.mp4 -Audio music.mp3
|
||||||
|
# Replaces the video's audio with audio from music.mp3
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clone frame range with alternate audio:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source video.mp4 -Target clip.mp4 -StartFrame 100 -EndFrame 500 -Audio soundtrack.wav
|
||||||
|
# Clones frames 100-500 and uses audio from soundtrack.wav (trimmed to match video duration)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clone using the closest LTX-2 720p bucket:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source video.mp4 -Target ltx720.mp4 -Size 720p -LTX
|
||||||
|
# A 16:9 source is rescaled to 1280x704, a 9:16 source to 704x1280,
|
||||||
|
# a 21:9 source to 1088x576, a 9:21 source to 576x1088.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clone using the closest LTX-2 1080p bucket:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source phone.mp4 -Target ltx1080.mp4 -Size 1080p -LTX
|
||||||
|
# A 1080x1920 (9:16) phone clip is rescaled to 1088x1920.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clone with custom CRF value:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source video.mp4 -Target output.mp4 -Size 1080p -CRF 18
|
||||||
|
# Uses CRF 18 instead of default CRF 8 or source bitrate
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clone with metadata:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source video.mp4 -Target output.mp4 -Metadata
|
||||||
|
# Copies all available JSON metadata from source to target
|
||||||
|
```
|
||||||
|
|
||||||
|
**Reverse the video (audio stays forward):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source video.mp4 -Target reversed.mp4 -Reverse
|
||||||
|
# Video frames are played from last to first; audio plays forward as in the source.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Reverse a trimmed segment, rescaled and re-timed:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source video.mp4 -Target clip_rev.mp4 -StartFrame 100 -EndFrame 500 -Size 720p -FPS 30 -Reverse
|
||||||
|
# Selects frames 100-500, scales to 720p, retimes to 30 fps, then reverses the video.
|
||||||
|
# Audio is trimmed to match the same segment but plays forward.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Build video from a still image with audio (image-to-video mode):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source photo.jpg -Target output.mp4 -Audio soundtrack.mp3
|
||||||
|
# Creates a video of the static image for the duration of soundtrack.mp3
|
||||||
|
```
|
||||||
|
|
||||||
|
**Build image-to-video at a specific resolution:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source photo.png -Target output.mp4 -Audio narration.wav -Size 720p
|
||||||
|
```
|
||||||
|
|
||||||
|
**Build image-to-video with LTX-2 resolution:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source photo.jpg -Target ltx.mp4 -Audio narration.wav -Size 1080p -LTX
|
||||||
|
# Picks the closest LTX-2 1080p bucket for the image's aspect ratio
|
||||||
|
```
|
||||||
|
|
||||||
|
**Build video from PNG sequence (all defaults):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source frames\ -Target output.mp4
|
||||||
|
# All PNG files in frames\ sorted alphabetically, 24 fps, CRF 8, no audio
|
||||||
|
```
|
||||||
|
|
||||||
|
**Build from a frame range:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source frames\ -Target clip.mp4 -StartFrame 10 -EndFrame 49
|
||||||
|
# Uses frame_0010.png through frame_0049.png (0-based indices into the sorted list)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Build reversed sequence:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source frames\ -Target reversed.mp4 -Reverse
|
||||||
|
# PNG files sorted descending: last frame plays first
|
||||||
|
```
|
||||||
|
|
||||||
|
**Build reversed range:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source frames\ -Target clip_rev.mp4 -StartFrame 10 -EndFrame 49 -Reverse
|
||||||
|
# Frames 10-49 selected first, then reversed: frame_0049.png plays first
|
||||||
|
```
|
||||||
|
|
||||||
|
**Build with custom FPS and CRF:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source frames\ -Target output.mp4 -FPS 30 -CRF 18
|
||||||
|
```
|
||||||
|
|
||||||
|
**Build with scaled output:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source frames\ -Target output.mp4 -Size 720p
|
||||||
|
# Scales to 720p preserving aspect ratio of first PNG
|
||||||
|
```
|
||||||
|
|
||||||
|
**Build with audio:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source frames\ -Target output.mp4 -Audio soundtrack.mp3
|
||||||
|
```
|
||||||
|
|
||||||
|
**Build with LTX-2 canonical resolution:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source frames\ -Target ltx.mp4 -Size 720p -LTX
|
||||||
|
# Picks the closest LTX-2 720p bucket for the first PNG's aspect ratio
|
||||||
|
```
|
||||||
|
|
||||||
|
**Build as WebM:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source frames\ -Target output.webm -FPS 24
|
||||||
|
# VP9 + no audio; use -Audio to add a soundtrack
|
||||||
|
```
|
||||||
|
|
||||||
|
**Using absolute paths:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source "C:\Videos\input.mp4" -Target "D:\Output\result.mp4"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Convert a YouTube/Google DASH download (VP9 + Opus in WebM) to standard H.264 MP4:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source download.webm -Target download.mp4
|
||||||
|
# Video is transcoded from VP9 to H.264 (libx264, yuv420p, slow preset)
|
||||||
|
# Audio is transcoded from Opus to AAC 192k
|
||||||
|
```
|
||||||
|
|
||||||
|
**Convert an MKV source (e.g. yt-dlp default) to MP4:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source movie.mkv -Target movie.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
**Convert and rescale a VP9 WebM in one step:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source download.webm -Target download.mp4 -Size 720p
|
||||||
|
# Single re-encode pass: VP9 -> H.264 at 720p
|
||||||
|
```
|
||||||
|
|
||||||
|
**Transcode an MP4 to WebM (VP9 + Opus):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source video.mp4 -Target video.webm
|
||||||
|
# Video transcoded H.264 -> VP9 (libvpx-vp9)
|
||||||
|
# Audio transcoded AAC -> Opus (libopus, 160k)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Stream-copy a YouTube WebM into MKV (no re-encoding):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source download.webm -Target download.mkv
|
||||||
|
# MKV accepts VP9 and Opus directly; both streams are stream-copied.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output containers
|
||||||
|
|
||||||
|
The output container is selected from the `-Target` file extension. Each container has its own codec profile, stream-copy list, and encoder settings.
|
||||||
|
|
||||||
|
| Container | Target video codec | Target audio codec | Source video codecs stream-copied | Source audio codecs stream-copied |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `.mp4` (default) | `libx264` (yuv420p, preset slow) | `aac` 192k | `h264` | `aac`, `mp3`, `ac3`, `eac3`, `alac` |
|
||||||
|
| `.webm` | `libvpx-vp9` (yuv420p, deadline good, cpu-used 2, row-mt 1) | `libopus` 160k | `vp8`, `vp9`, `av1` | `opus`, `vorbis` |
|
||||||
|
| `.mkv` | `libx264` (yuv420p, preset slow) | `aac` 192k | `h264`, `hevc`, `vp8`, `vp9`, `av1`, `mpeg4` | `aac`, `mp3`, `ac3`, `eac3`, `alac`, `flac`, `opus`, `vorbis` |
|
||||||
|
|
||||||
|
### General rules
|
||||||
|
|
||||||
|
- A source stream that's on the container's stream-copy list is copied without re-encoding when no trimming, resizing, frame rate change, or audio removal is requested.
|
||||||
|
- A source stream that's **not** on the stream-copy list is transcoded to the container's target codec (e.g. VP9 → H.264 when the target is MP4, AAC → Opus when the target is WebM).
|
||||||
|
- Trimming (`-StartFrame`/`-EndFrame` or `-Frames`) always forces an audio re-encode; in that case the audio is encoded with the target container's audio codec regardless of the source.
|
||||||
|
- `-Size` always forces a video re-encode (scaling requires re-encoding). For standard named sizes (`320p`, `384p`, `480p`, `720p`, `900p`, `1080p`/`HD`, `1440p`/`2K`, `2160p`/`4K`) the output preserves the source's aspect ratio — the reference height is the short-side target (landscape: computed width; portrait: computed height). Custom sizes (`width:height`) are used exactly.
|
||||||
|
- `-Crop` replaces the `scale` filter with a centered `crop` filter for the size computed from `-Size`/`-LTX`; it forces a video re-encode like `-Size`.
|
||||||
|
- `-FPS` always forces a video re-encode (frame rate conversion requires re-encoding).
|
||||||
|
- `-NoAudio` always forces a video re-encode (removing the audio stream requires re-muxing the output file).
|
||||||
|
- `-Reverse` always forces a video re-encode (the `reverse` ffmpeg filter is appended at the end of the video filter chain). Audio is never reversed, so it plays forward over the reversed video and follows the same stream-copy/transcode rules it would have without `-Reverse`.
|
||||||
|
- `-LTX` only takes effect when `-Size` is `HD`, `1080p`, `720p` or `480p`. Combining it with any other `-Size` value (or omitting `-Size`) is rejected with an explicit error so the user is never silently downgraded to the regular preset. The chosen bucket replaces the dimensions that `-Size` would normally produce; everything else (re-encode logic, frame range, FPS, CRF, audio handling) behaves exactly as with a plain `-Size`.
|
||||||
|
- Video bitrate priority is the same for every container: `-CRF` (when specified) → source video bitrate (via `-b:v`) → `-crf 14` fallback. For `libvpx-vp9`, `-b:v 0` is automatically added alongside `-crf` so VP9's constant-quality mode is correctly enabled.
|
||||||
|
- The `-Audio` replacement file is also probed; if its codec isn't on the target container's audio stream-copy list, it is transcoded to the container's audio codec.
|
||||||
|
- If `-Target` has no extension (or an unrecognised one), `.mp4` is appended and the MP4 profile is used. This preserves the script's prior single-container behaviour.
|
||||||
|
- The script logs the chosen target container, the detected source codecs, and any external `-Audio` codec, indicating whether each will be transcoded before running ffmpeg.
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
- **WebM encoding is slow.** `libvpx-vp9` is much slower than `libx264`; the chosen `cpu-used 2 -row-mt 1` settings strike a reasonable balance, but expect significantly longer rendering times than MP4.
|
||||||
|
- **MKV stream-copy is greedy.** Because MKV accepts almost any codec, MKV output usually involves no re-encoding at all and is the fastest target for re-containerising downloads.
|
||||||
|
|
||||||
|
## Safe Execution
|
||||||
|
|
||||||
|
This script supports `-WhatIf` and `-Confirm` for safe execution:
|
||||||
|
|
||||||
|
- **`-WhatIf`**: Simulate the operation without actually creating files. Shows what would be done.
|
||||||
|
- **`-Confirm`**: Prompt for confirmation before each state-changing operation.
|
||||||
|
|
||||||
|
**Simulate without making changes:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source video.mp4 -Target output.mp4 -WhatIf
|
||||||
|
```
|
||||||
|
|
||||||
|
**Prompt before creating files:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Clone-Video.ps1 -Source video.mp4 -Target output.mp4 -Sequence frame.png -Confirm
|
||||||
|
```
|
||||||
@@ -0,0 +1,445 @@
|
|||||||
|
# Compare-Videos.ps1
|
||||||
|
|
||||||
|
Combines 2 to 4 video files into a single comparison video using ffmpeg.
|
||||||
|
|
||||||
|
<video controls width="100%"><source src="../../../images/Compare-Videos.mp4" type="video/mp4"></video>
|
||||||
|
|
||||||
|
*Left side: Original video, Right side: Outpainted video using LTX-2.3 and Wan2GP.*
|
||||||
|
|
||||||
|
*See [Create-PillarBox.ps1](Create-PillarBox.md) script for an example with 4 videos.*
|
||||||
|
|
||||||
|
## Synopsis
|
||||||
|
|
||||||
|
Creates side-by-side or grid comparison videos with automatic scaling and padding to preserve aspect ratios. By default, creates 1920x1080 (1K HD) output. Use `-In4K` for 3840x2160 (4K UHD) output. The script detects the orientation of the first video (S1) and applies appropriate scaling for either landscape or portrait mode. Each source video is scaled to fit its cell without stretching, with black letterbox/pillarbox padding applied as needed.
|
||||||
|
|
||||||
|
When exactly two sources already tile the output frame perfectly, they are passed through untouched — see [Exact Fit](#exact-fit).
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
- **`-S1`** (required): Path to source video 1
|
||||||
|
- **`-S2`** (required): Path to source video 2
|
||||||
|
- **`-S3`** (optional): Path to source video 3
|
||||||
|
- **`-S4`** (optional): Path to source video 4
|
||||||
|
- **`-Output`** (required): Path to the output video file
|
||||||
|
- **`-NoAudio`**: Exclude audio from output (default: copies audio from S1)
|
||||||
|
- **`-In4K`**: Generate comparison video in 4K UHD (3840x2160) resolution instead of 1K HD (1920x1080)
|
||||||
|
- **`-Clip`**: Removes black padding from the output. Ignored when the two sources are an [exact fit](#exact-fit) for the output frame, since there is no padding to remove. For landscape, only applies when exactly **2** source videos are provided; ignored otherwise. For portrait, applies when **2–4** source videos are provided (all sources must be portrait); for 2–3 videos removes the outer side (left/right) padding, for 4 videos removes the outer top/bottom padding. With one landscape source and one portrait source (2-video only), switches to a **mixed-orientation** layout: the landscape video keeps its native pixel size (or 2× in `-In4K`) and the portrait video is centered in the remaining width with black pillar boxes. See [Clip Mode](#clip-mode) for details.
|
||||||
|
- **`-Sideways`**: Arranges all videos in a single horizontal row. For landscape mode, replaces the default 2x2 grid layout (used for 3 and 4 videos) with a single row. For portrait mode, the layout is the same as the default (already side-by-side). Can be combined with `-Clip` to remove black padding. See [Sideways Mode](#sideways-mode) for details.
|
||||||
|
|
||||||
|
## Exact Fit
|
||||||
|
|
||||||
|
With exactly **2** source videos, when both are already as tall as the output frame and their widths add up to the output width, placing them side by side *is* the target frame. In this case the script applies **no scaling and no padding at all** — the two streams go straight into `hstack`, so neither source is resampled and no quality is lost.
|
||||||
|
|
||||||
|
The condition is checked against the selected output resolution:
|
||||||
|
|
||||||
|
| | Required height (both sources) | Required width (S1 + S2) |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Default (1K HD) | `1080` | `1920` |
|
||||||
|
| With `-In4K` | `2160` | `3840` |
|
||||||
|
|
||||||
|
The widths do not have to be equal — any split that sums to the output width qualifies.
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
| S1 | S2 | Output | Exact fit? |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `1280x1080` | `640x1080` | `1920x1080` | Yes — 1280 + 640 = 1920, both 1080 tall |
|
||||||
|
| `960x1080` | `960x1080` | `1920x1080` | Yes — equal halves |
|
||||||
|
| `1440x1080` | `480x1080` | `1920x1080` | Yes — uneven split still sums to 1920 |
|
||||||
|
| `2560x2160` + `-In4K` | `1280x2160` | `3840x2160` | Yes — 2560 + 1280 = 3840, both 2160 tall |
|
||||||
|
| `2560x2160` | `1280x2160` | `1920x1080` | No — heights are not 1080 (add `-In4K`) |
|
||||||
|
| `1280x720` | `640x1080` | `1920x1080` | No — S1 is not 1080 tall |
|
||||||
|
| `1280x1080` | `680x1080` | `1920x1080` | No — widths sum to 1960, not 1920 |
|
||||||
|
|
||||||
|
Resulting filter graph (note the absence of `scale` and `pad`):
|
||||||
|
|
||||||
|
```text
|
||||||
|
[0:v][1:v]hstack=inputs=2[outv]
|
||||||
|
```
|
||||||
|
|
||||||
|
The console reports it as:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Exact fit: 1280x1080 + 640x1080 already tile 1920x1080; sources used unscaled.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Interaction with other switches
|
||||||
|
|
||||||
|
- **`-Clip`** is ignored: an exact-fit output contains no padding to remove. A notice is printed.
|
||||||
|
- **`-Sideways`** is ignored: two sources side by side are already a single row. A notice is printed.
|
||||||
|
- Applies to 2 sources only. With 3 or 4 videos the normal grid/row layouts are used.
|
||||||
|
- Orientation detection still runs, but is irrelevant — the exact-fit check takes precedence over both the landscape and portrait layouts. (A pair such as two `960x1080` sources is detected as "portrait", yet is now passed through instead of being squeezed into 608-wide cells.)
|
||||||
|
|
||||||
|
## Orientation Detection
|
||||||
|
|
||||||
|
The script automatically detects the orientation of S1 by comparing its width and height:
|
||||||
|
|
||||||
|
- **Landscape mode:** Width > Height (e.g., 1920x1080)
|
||||||
|
- **Portrait mode:** Height > Width (e.g., 1080x1920)
|
||||||
|
|
||||||
|
All videos are then scaled according to the detected orientation.
|
||||||
|
|
||||||
|
## Layout Rules
|
||||||
|
|
||||||
|
Each source video is scaled to fit its cell without stretching. Aspect ratio is always preserved using `force_original_aspect_ratio=decrease` combined with black padding to fill the cell exactly.
|
||||||
|
|
||||||
|
### Landscape Mode (width > height)
|
||||||
|
|
||||||
|
#### 1K HD (1920x1080) - Default
|
||||||
|
|
||||||
|
**2 videos:** Output is 1920x1080. Each video occupies a 960x1080 cell side-by-side.
|
||||||
|
|
||||||
|
```text
|
||||||
|
[S1][S2]
|
||||||
|
```
|
||||||
|
|
||||||
|
**3 videos:** Output is 1920x1080. Top row has two 960x540 cells (S1 left, S2 right). Bottom row has S3 in a 960x540 cell, horizontally centered with 480px black padding on each side.
|
||||||
|
|
||||||
|
```text
|
||||||
|
[S1][S2]
|
||||||
|
[ S3 ]
|
||||||
|
```
|
||||||
|
|
||||||
|
**4 videos:** Output is 1920x1080. Four 960x540 cells in a 2x2 grid.
|
||||||
|
|
||||||
|
```text
|
||||||
|
[S1][S2]
|
||||||
|
[S3][S4]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4K UHD (3840x2160) - With -In4K
|
||||||
|
|
||||||
|
**2 videos:** Output is 3840x2160. Each video occupies a 1920x2160 cell side-by-side.
|
||||||
|
|
||||||
|
```text
|
||||||
|
[S1][S2]
|
||||||
|
```
|
||||||
|
|
||||||
|
**3 videos:** Output is 3840x2160. Top row has two 1920x1080 cells (S1 left, S2 right). Bottom row has S3 in a 1920x1080 cell, horizontally centered with 960px black padding on each side.
|
||||||
|
|
||||||
|
```text
|
||||||
|
[S1][S2]
|
||||||
|
[ S3 ]
|
||||||
|
```
|
||||||
|
|
||||||
|
**4 videos:** Output is 3840x2160. Four 1920x1080 cells in a 2x2 grid.
|
||||||
|
|
||||||
|
```text
|
||||||
|
[S1][S2]
|
||||||
|
[S3][S4]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Portrait Mode (height > width)
|
||||||
|
|
||||||
|
#### 1K HD (1920x1080) - Default
|
||||||
|
|
||||||
|
All videos are displayed horizontally side-by-side, scaled and centered with black padding.
|
||||||
|
|
||||||
|
**2 videos:** Each video is scaled to 608x1080. Combined width is 1216x1080, centered horizontally in 1920x1080 output with 352px padding on each side. With `-Clip`: 1216x1080 (no side padding).
|
||||||
|
|
||||||
|
```text
|
||||||
|
[ ][S1][S2][ ]
|
||||||
|
```
|
||||||
|
|
||||||
|
**3 videos:** Each video is scaled to 608x1080. Combined width is 1824x1080, centered horizontally in 1920x1080 output with 48px padding on each side. With `-Clip`: 1824x1080 (all sources must be portrait; otherwise falls back to padded layout).
|
||||||
|
|
||||||
|
```text
|
||||||
|
[ ][S1][S2][S3][ ]
|
||||||
|
```
|
||||||
|
|
||||||
|
**4 videos:** Each video is scaled to 480x854. Combined width is 1920x854, centered vertically in 1920x1080 output with 113px padding on top and bottom. With `-Clip`: 1920x854 (all sources must be portrait; otherwise falls back to padded layout).
|
||||||
|
|
||||||
|
```text
|
||||||
|
[S1][S2][S3][S4]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4K UHD (3840x2160) - With -In4K
|
||||||
|
|
||||||
|
**2 videos:** Each video is scaled to 1216x2160. Combined width is 2432x2160, centered horizontally in 3840x2160 output with 704px padding on each side. With `-Clip`: 2432x2160 (no side padding).
|
||||||
|
|
||||||
|
```text
|
||||||
|
[ ][S1][S2][ ]
|
||||||
|
```
|
||||||
|
|
||||||
|
**3 videos:** Each video is scaled to 1216x2160. Combined width is 3648x2160, centered horizontally in 3840x2160 output with 96px padding on each side. With `-Clip`: 3648x2160 (all sources must be portrait; otherwise falls back to padded layout).
|
||||||
|
|
||||||
|
```text
|
||||||
|
[ ][S1][S2][S3][ ]
|
||||||
|
```
|
||||||
|
|
||||||
|
**4 videos:** Each video is scaled to 960x1708. Combined width is 3840x1708, centered vertically in 3840x2160 output with 226px padding on top and bottom. With `-Clip`: 3840x1708 (all sources must be portrait; otherwise falls back to padded layout).
|
||||||
|
|
||||||
|
```text
|
||||||
|
[S1][S2][S3][S4]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sideways Mode
|
||||||
|
|
||||||
|
With `-Sideways`, all videos are placed in a single horizontal row regardless of video count. For landscape sources this is the key difference from the default layout, which uses a 2x2 grid for 3 and 4 videos.
|
||||||
|
|
||||||
|
Can be combined with `-Clip` to remove black padding: for landscape, the output height is reduced to the natural content height of the sources; for portrait, the outer centering pad is omitted and the output matches the raw hstack dimensions.
|
||||||
|
|
||||||
|
### Landscape Mode — Sideways
|
||||||
|
|
||||||
|
#### 1K HD (1920x1080)
|
||||||
|
|
||||||
|
**2 videos:** Each cell is 960x1080. Video content scales to 960x540, padded vertically. Total: 1920x1080. With `-Clip`: total 1920x540.
|
||||||
|
|
||||||
|
```text
|
||||||
|
[S1][S2]
|
||||||
|
```
|
||||||
|
|
||||||
|
**3 videos:** Each cell is 640x1080. Video content scales to 640x360, padded vertically. Total: 1920x1080. With `-Clip`: total 1920x360.
|
||||||
|
|
||||||
|
```text
|
||||||
|
[S1][S2][S3]
|
||||||
|
```
|
||||||
|
|
||||||
|
**4 videos:** Each cell is 480x1080. Video content scales to 480x270, padded vertically. Total: 1920x1080. With `-Clip`: total 1920x270.
|
||||||
|
|
||||||
|
```text
|
||||||
|
[S1][S2][S3][S4]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4K UHD (3840x2160) — With -In4K
|
||||||
|
|
||||||
|
**2 videos:** Each cell is 1920x2160. Video content scales to 1920x1080, padded vertically. Total: 3840x2160. With `-Clip`: total 3840x1080.
|
||||||
|
|
||||||
|
**3 videos:** Each cell is 1280x2160. Video content scales to 1280x720, padded vertically. Total: 3840x2160. With `-Clip`: total 3840x720.
|
||||||
|
|
||||||
|
**4 videos:** Each cell is 960x2160. Video content scales to 960x540, padded vertically. Total: 3840x2160. With `-Clip`: total 3840x540.
|
||||||
|
|
||||||
|
### Portrait Mode — Sideways
|
||||||
|
|
||||||
|
Portrait videos are already displayed side-by-side by default. The `-Sideways` switch produces the same layout as the standard portrait mode. With `-Clip`, the outer black padding is omitted and the output matches the raw hstack dimensions.
|
||||||
|
|
||||||
|
#### 1K HD (1920x1080)
|
||||||
|
|
||||||
|
**2 videos:** Each cell 608x1080. Output 1920x1080 (352px padding per side). With `-Clip`: 1216x1080.
|
||||||
|
|
||||||
|
**3 videos:** Each cell 608x1080. Output 1920x1080 (48px padding per side). With `-Clip`: 1824x1080.
|
||||||
|
|
||||||
|
**4 videos:** Each cell 480x854. Output 1920x1080 (~113px padding top/bottom). With `-Clip`: 1920x854.
|
||||||
|
|
||||||
|
#### 4K UHD (3840x2160) — With -In4K
|
||||||
|
|
||||||
|
**2 videos:** Each cell 1216x2160. Output 3840x2160 (704px padding per side). With `-Clip`: 2432x2160.
|
||||||
|
|
||||||
|
**3 videos:** Each cell 1216x2160. Output 3840x2160 (96px padding per side). With `-Clip`: 3648x2160.
|
||||||
|
|
||||||
|
**4 videos:** Each cell 960x1706. Output 3840x2160 (~227px padding top/bottom). With `-Clip`: 3840x1706.
|
||||||
|
|
||||||
|
## Clip Mode
|
||||||
|
|
||||||
|
With only 2 source videos, the default behavior pads the output to the full target resolution (1920x1080 or 3840x2160). When the sources are shorter than 16:9 (e.g. two 1280x720 videos placed side-by-side at 1K HD), this leaves significant black bars on the top and bottom. The `-Clip` switch removes those bars. It works in two modes depending on the source orientations.
|
||||||
|
|
||||||
|
### Same-orientation mode (both landscape or both portrait)
|
||||||
|
|
||||||
|
The final output height is reduced to the actual height each source naturally scales to inside its half-width cell, removing the top/bottom letterbox entirely.
|
||||||
|
|
||||||
|
#### How it works
|
||||||
|
|
||||||
|
1. Both `S1` and `S2` are probed for their original `width x height`.
|
||||||
|
2. For each source, the script computes the height it would scale to when fitted by width into its cell (`outputWidth / 2` for landscape, `outputWidth * 0.6333 / 2` for portrait), preserving aspect ratio (`force_original_aspect_ratio=decrease`).
|
||||||
|
3. The larger of the two scaled heights is used as the new output height (rounded up to an even number for `libx264`/`yuv420p`), capped at the original output height.
|
||||||
|
4. All subsequent scaling/padding uses that reduced output height, so neither source is cropped and there is no top/bottom letterbox.
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
| Sources | Default output | With `-Clip` | With `-Clip -In4K` |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| Two `1280x720` (16:9) | `1920x1080` | `1920x540` | `3840x1080` |
|
||||||
|
| Two `1920x1080` (16:9) | `1920x1080` | `1920x1080` (already full height) | `3840x2160` (already full height) |
|
||||||
|
| One `1280x720` + one `1280x960` | `1920x1080` | `1920x720` (max scaled height) | `3840x1440` |
|
||||||
|
|
||||||
|
### Mixed-orientation mode (one landscape + one portrait)
|
||||||
|
|
||||||
|
When one source is landscape and the other is portrait, `-Clip` switches to a layout designed to display the landscape video at its native pixel size while pillar-boxing the portrait into the remaining horizontal space.
|
||||||
|
|
||||||
|
#### How it works
|
||||||
|
|
||||||
|
1. Both `S1` and `S2` are probed; the script detects that one is landscape (width > height) and the other is portrait.
|
||||||
|
2. A `scaleFactor` is derived from the target resolution: `1.0` at 1K HD, `2.0` at 4K UHD (`outputWidth / 1920`).
|
||||||
|
3. The landscape source is rendered at `landscapeNativeWidth * scaleFactor` × `landscapeNativeHeight * scaleFactor`. At 1K HD this is its native pixel size; at 4K UHD it's exactly 2× upscaled, matching the `-In4K` doubling used elsewhere.
|
||||||
|
4. The output height is set to the landscape's scaled height.
|
||||||
|
5. The portrait source is scaled to fit the remaining width (`outputWidth - landscapeScaledWidth`) × output height with `force_original_aspect_ratio=decrease`, then padded with black to fill that cell exactly (pillar-box bars).
|
||||||
|
6. `S1` is placed on the left and `S2` on the right (the script preserves the same S1/S2 ordering as the same-orientation layout, regardless of which one is landscape).
|
||||||
|
7. If the landscape source is already as wide as the target resolution (e.g. a 1080p source at `-In4K` would scale to 3840 wide, leaving no room for the portrait), the script falls back to the same-orientation logic and emits a console warning.
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
| Sources (S1, S2) | `-Clip` output | `-Clip -In4K` output |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `1280x720` landscape + `720x1280` portrait | `1920x720` (landscape `1280x720` on its side, portrait cell `640x720`) | `3840x1440` (landscape `2560x1440`, portrait cell `1280x1440`) |
|
||||||
|
| `720x1280` portrait + `1280x720` landscape | `1920x720` (portrait cell `640x720` on left as S1, landscape `1280x720` on right as S2) | `3840x1440` (same with `-In4K` doubling) |
|
||||||
|
| `854x480` landscape + `480x854` portrait | `1920x480` (landscape `854x480`, portrait cell `1066x480`) | `3840x960` |
|
||||||
|
| `1920x1080` landscape + `1080x1920` portrait | falls back to same-orientation behaviour (landscape already at target width) | falls back to same-orientation behaviour |
|
||||||
|
|
||||||
|
### Portrait clip mode
|
||||||
|
|
||||||
|
When all source videos are portrait, `-Clip` removes the outer black padding rather than adjusting height:
|
||||||
|
|
||||||
|
- **2 or 3 videos:** The outer side (left/right) padding is omitted. Output width equals the raw hstack width (2× or 3× the cell width).
|
||||||
|
- **4 videos:** The outer top/bottom padding is omitted. Output height equals the natural 9:16 cell height.
|
||||||
|
|
||||||
|
For 3 and 4 videos, S2 (and S3/S4 as needed) are probed to verify all sources are portrait. If any source is not portrait, the full padded layout is used and a warning is emitted.
|
||||||
|
|
||||||
|
| Sources | Default output | With `-Clip` | With `-Clip -In4K` |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 2 portrait videos | 1920x1080 | 1216x1080 | 2432x2160 |
|
||||||
|
| 3 portrait videos (all portrait) | 1920x1080 | 1824x1080 | 3648x2160 |
|
||||||
|
| 4 portrait videos (all portrait) | 1920x1080 | 1920x854 | 3840x1708 |
|
||||||
|
|
||||||
|
### Restrictions
|
||||||
|
|
||||||
|
- For landscape, only applies when exactly **2** source videos are provided. With 3 or 4 landscape videos, the switch is ignored and a warning is emitted.
|
||||||
|
- For portrait, applies when **2–4** source videos are provided. For 3 and 4 videos, all sources must be portrait; if any is not, the padded layout is used automatically.
|
||||||
|
- Mixed-orientation mode (one landscape + one portrait) requires the landscape source's scaled width to be strictly less than the target width and its scaled height to fit within the target height; otherwise the script falls back to the same-orientation behaviour automatically.
|
||||||
|
- The audio handling, codec, and CRF are unchanged.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
**Exact fit — two videos that already tile 1920x1080 (no scaling, no padding):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Compare-Videos.ps1 -S1 wide_1280x1080.mp4 -S2 narrow_640x1080.mp4 -Output exact.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
**Exact fit at 4K (2560x2160 + 1280x2160 → 3840x2160):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Compare-Videos.ps1 -S1 wide_2560x2160.mp4 -S2 narrow_1280x2160.mp4 -Output exact_4k.mp4 -In4K
|
||||||
|
```
|
||||||
|
|
||||||
|
**Two portrait videos clipped to remove side padding (1216x1080):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Compare-Videos.ps1 -S1 portrait1.mp4 -S2 portrait2.mp4 -Output portrait_clip.mp4 -Clip
|
||||||
|
```
|
||||||
|
|
||||||
|
**Same pair at 4K (2432x2160):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Compare-Videos.ps1 -S1 portrait1.mp4 -S2 portrait2.mp4 -Output portrait_clip_4k.mp4 -Clip -In4K
|
||||||
|
```
|
||||||
|
|
||||||
|
**Three portrait videos clipped (1824x1080 — all sources must be portrait):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Compare-Videos.ps1 -S1 p1.mp4 -S2 p2.mp4 -S3 p3.mp4 -Output portrait3_clip.mp4 -Clip
|
||||||
|
```
|
||||||
|
|
||||||
|
**Four portrait videos clipped to remove top/bottom padding (1920x854 — all sources must be portrait):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Compare-Videos.ps1 -S1 p1.mp4 -S2 p2.mp4 -S3 p3.mp4 -S4 p4.mp4 -Output portrait4_clip.mp4 -Clip
|
||||||
|
```
|
||||||
|
|
||||||
|
**Compare two videos:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Compare-Videos.ps1 -S1 original.mp4 -S2 enhanced.mp4 -Output comparison.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
**Compare three videos:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Compare-Videos.ps1 -S1 version1.mp4 -S2 version2.mp4 -S3 version3.mp4 -Output comparison.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
**Compare four videos in grid:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Compare-Videos.ps1 -S1 a.mp4 -S2 b.mp4 -S3 c.mp4 -S4 d.mp4 -Output grid.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
**Without audio:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Compare-Videos.ps1 -S1 video1.mp4 -S2 video2.mp4 -Output comparison.mp4 -NoAudio
|
||||||
|
```
|
||||||
|
|
||||||
|
**Using absolute paths:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Compare-Videos.ps1 -S1 "C:\Videos\original.mp4" -S2 "D:\Processed\enhanced.mp4" -Output "E:\Output\comparison.mp4"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Generate 4K UHD comparison:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Compare-Videos.ps1 -S1 original.mp4 -S2 enhanced.mp4 -Output comparison_4k.mp4 -In4K
|
||||||
|
```
|
||||||
|
|
||||||
|
**Two 720p videos clipped to a true 16:9 strip (1920x540):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Compare-Videos.ps1 -S1 a_720p.mp4 -S2 b_720p.mp4 -Output side-by-side.mp4 -Clip
|
||||||
|
```
|
||||||
|
|
||||||
|
**Same pair at 4K (3840x1080):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Compare-Videos.ps1 -S1 a_720p.mp4 -S2 b_720p.mp4 -Output side-by-side_4k.mp4 -Clip -In4K
|
||||||
|
```
|
||||||
|
|
||||||
|
**Mixed orientation: 720p landscape + 720x1280 portrait (1920x720 output):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Compare-Videos.ps1 -S1 landscape_720p.mp4 -S2 portrait_720x1280.mp4 -Output mixed.mp4 -Clip
|
||||||
|
# Landscape occupies 1280x720 on the left, portrait fits in the right 640x720 cell with pillar boxes.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Same mixed pair at 4K (3840x1440 output):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Compare-Videos.ps1 -S1 landscape_720p.mp4 -S2 portrait_720x1280.mp4 -Output mixed_4k.mp4 -Clip -In4K
|
||||||
|
# Landscape is upscaled to 2560x1440, portrait cell is 1280x1440.
|
||||||
|
```
|
||||||
|
|
||||||
|
**4K comparison with four videos:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Compare-Videos.ps1 -S1 a.mp4 -S2 b.mp4 -S3 c.mp4 -S4 d.mp4 -Output grid_4k.mp4 -In4K
|
||||||
|
```
|
||||||
|
|
||||||
|
**Three landscape videos in a single row (640x1080 cells):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Compare-Videos.ps1 -S1 a.mp4 -S2 b.mp4 -S3 c.mp4 -Output sideways.mp4 -Sideways
|
||||||
|
```
|
||||||
|
|
||||||
|
**Four landscape videos in a single row at 4K (960x2160 cells):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Compare-Videos.ps1 -S1 a.mp4 -S2 b.mp4 -S3 c.mp4 -S4 d.mp4 -Output sideways_4k.mp4 -Sideways -In4K
|
||||||
|
```
|
||||||
|
|
||||||
|
**Three landscape videos in a single row, clipped to content height (1920x360 output):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Compare-Videos.ps1 -S1 a.mp4 -S2 b.mp4 -S3 c.mp4 -Output sideways_clip.mp4 -Sideways -Clip
|
||||||
|
```
|
||||||
|
|
||||||
|
## Encoding Settings
|
||||||
|
|
||||||
|
- **Codec:** libx264
|
||||||
|
- **Preset:** fast
|
||||||
|
- **CRF:** 8 (high quality)
|
||||||
|
- **Pixel Format:** yuv420p
|
||||||
|
- **Audio:** Copied from S1 (unless `-NoAudio` specified)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Requires `ffmpeg` and `ffprobe` in system PATH
|
||||||
|
- All source video files and output directory must exist before execution
|
||||||
|
- The script probes S1 to determine orientation (landscape vs. portrait)
|
||||||
|
- All videos are scaled according to the detected orientation of S1
|
||||||
|
- The full ffmpeg command is printed to console before execution
|
||||||
|
- Supports `-WhatIf` for dry-run testing without creating output
|
||||||
|
- Videos with different aspect ratios are automatically letterboxed/pillarboxed with black padding
|
||||||
|
- Source videos are never stretched or distorted
|
||||||
|
- With 2 sources that exactly tile the output frame, no `scale`/`pad` filter is applied at all; see [Exact Fit](#exact-fit)
|
||||||
|
- Portrait mode videos are always displayed side-by-side horizontally, not in a grid
|
||||||
|
- Use `-Sideways` to force all videos into a single horizontal row; this replaces the default 2×2 grid for landscape 3 and 4 videos
|
||||||
|
- `-Sideways` and `-Clip` can be combined: for landscape, the cell height is reduced to the natural content height; for portrait, the outer centering padding is omitted
|
||||||
+171
@@ -0,0 +1,171 @@
|
|||||||
|
# Create-Clip.ps1
|
||||||
|
|
||||||
|
Creates a clipped video file by calling [Clone-Video.ps1](Clone-Video.md) with an automatically derived output filename. The `-Target` path is generated from the `-Video` filename by inserting the `-Suffix` value (default `-Clip`) before the file extension — for example, `P01.mp4` becomes `P01-Clip.mp4` saved alongside the source. Both `-Video` and `-Frames` are required; any additional parameters are forwarded directly to `Clone-Video.ps1`.
|
||||||
|
|
||||||
|
Sources carrying a `.skip` marker keep that marker last: `P01.skip.mp4` becomes `P01-Clip.skip.mp4`, so the clip remains excluded from the scripts that ignore `.skip` files.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
- **`-Video`** (required): Path to the input video file. Can be absolute or relative to current directory. Accepted containers: `.mp4`, `.webm`, `.mkv`. If the path has no extension the script tries `.mp4`, `.webm`, then `.mkv` in turn and uses the first match to determine the target filename extension.
|
||||||
|
- **`-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-%"`, `"-%"`)
|
||||||
|
- `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.
|
||||||
|
|
||||||
|
Refer to [Clone-Video.ps1](Clone-Video.md) for a full description of all supported parameters.
|
||||||
|
|
||||||
|
## Target Filename Derivation
|
||||||
|
|
||||||
|
The output filename is derived automatically from the resolved source path:
|
||||||
|
|
||||||
|
| Source | Target |
|
||||||
|
| --- | --- |
|
||||||
|
| `P01.mp4` | `P01-Clip.mp4` |
|
||||||
|
| `render/shot.webm` | `render/shot-Clip.webm` |
|
||||||
|
| `C:\Videos\take01.mkv` | `C:\Videos\take01-Clip.mkv` |
|
||||||
|
| `P01` (no extension, resolves to `P01.mp4`) | `P01-Clip.mp4` |
|
||||||
|
| `P01.skip.mp4` | `P01-Clip.skip.mp4` |
|
||||||
|
| `P04.skip.webm` | `P04-Clip.skip.webm` |
|
||||||
|
| `P01.skip` (no extension, resolves to `P01.skip.mp4`) | `P01-Clip.skip.mp4` |
|
||||||
|
| `P01.mp4` with `-Suffix "-Shot-1"` | `P01-Shot-1.mp4` |
|
||||||
|
| `P01.mp4` with `-Suffix "-Clip-Shot-1"` | `P01-Clip-Shot-1.mp4` |
|
||||||
|
| `P01.skip.mp4` with `-Suffix "-Clip-Shot-1"` | `P01-Clip-Shot-1.skip.mp4` |
|
||||||
|
|
||||||
|
### `.skip` sources
|
||||||
|
|
||||||
|
Several scripts in this collection ignore files whose name ends with `.skip.<ext>` (for example [Crop-Clips.ps1](Crop-Clips.md)). Inserting `-Clip` before the extension would produce `P01.skip-Clip.mp4`, which loses that meaning, so the marker is kept immediately before the container extension instead:
|
||||||
|
|
||||||
|
| Source | Target | |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `P01.skip.mp4` | `P01-Clip.skip.mp4` | correct — still a `.skip` file |
|
||||||
|
| `P01.skip.mp4` | `P01.skip-Clip.mp4` | not used — marker no longer last |
|
||||||
|
|
||||||
|
This applies to every accepted container (`.mp4`, `.webm`, `.mkv`), matching is case-insensitive, and the marker's original casing is preserved (`P03.SKIP.mkv` → `P03-Clip.SKIP.mkv`).
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
**Clip a specific frame range:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Clip.ps1 -Video P01.mp4 -Frames "100-200"
|
||||||
|
# Creates: P01-Clip.mp4 (frames 100 to 200)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clip from frame 0 to frame 50:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Clip.ps1 -Video P01.mp4 -Frames "50"
|
||||||
|
# Creates: P01-Clip.mp4 (frames 0 to 50)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clip last 10 frames:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Clip.ps1 -Video P01.mp4 -Frames "-10"
|
||||||
|
# Creates: P01-Clip.mp4 (last 10 frames)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clip from frame 10 to last frame:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Clip.ps1 -Video P01.mp4 -Frames "10-%"
|
||||||
|
# Creates: P01-Clip.mp4 (frames 10 to end)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clip with resized output:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Clip.ps1 -Video P01.mp4 -Frames "100-200" -Size 720p
|
||||||
|
# Creates: P01-Clip.mp4 (frames 100-200 scaled to 720p)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clip without audio:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Clip.ps1 -Video P01.mp4 -Frames "100-200" -NoAudio
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clip with alternate audio:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Clip.ps1 -Video P01.mp4 -Frames "50-150" -Audio music.mp3
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clip with custom CRF:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Clip.ps1 -Video P01.mp4 -Frames "100-500" -CRF 18
|
||||||
|
# Uses CRF 18 instead of default CRF 8 or source bitrate
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clip a reversed segment:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Clip.ps1 -Video P01.mp4 -Frames "100-200" -Reverse
|
||||||
|
# Creates: P01-Clip.mp4 playing frames 200 down to 100
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clip using the closest LTX-2 720p bucket:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Clip.ps1 -Video P01.mp4 -Frames "100-200" -Size 720p -LTX
|
||||||
|
```
|
||||||
|
|
||||||
|
**Source without extension:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Clip.ps1 -Video P01 -Frames "100-200"
|
||||||
|
# Resolves P01 -> P01.mp4 (or .webm / .mkv, whichever exists first)
|
||||||
|
# Creates: P01-Clip.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
**Replace the default `-Clip` suffix:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Clip.ps1 -Video P01.mp4 -Frames "100-200" -Suffix "-Clip-Shot-1"
|
||||||
|
# Creates: P01-Clip-Shot-1.mp4 (frames 100 to 200)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Using absolute paths:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Clip.ps1 -Video "C:\Videos\P01.mp4" -Frames "100-200"
|
||||||
|
# Creates: C:\Videos\P01-Clip.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clip a `.skip` source:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Clip.ps1 -Video P01.skip.mp4 -Frames "100-200"
|
||||||
|
# Creates: P01-Clip.skip.mp4 (the .skip marker stays before the extension)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clip a `.skip` source with extra parameters:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Clip.ps1 -Video P01.skip.mp4 -Frames "5-10" -Size 480p
|
||||||
|
# Creates: P01-Clip.skip.mp4 scaled to 480p
|
||||||
|
```
|
||||||
|
|
||||||
|
## Safe Execution
|
||||||
|
|
||||||
|
This script supports `-WhatIf` and `-Confirm` for safe execution (forwarded to `Clone-Video.ps1`):
|
||||||
|
|
||||||
|
- **`-WhatIf`**: Simulate the operation without creating files. Shows what `Clone-Video.ps1` would do.
|
||||||
|
- **`-Confirm`**: Prompt for confirmation before each state-changing operation.
|
||||||
|
|
||||||
|
**Simulate without making changes:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Clip.ps1 -Video P01.mp4 -Frames "100-200" -WhatIf
|
||||||
|
```
|
||||||
|
|
||||||
|
**Prompt before creating files:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Clip.ps1 -Video P01.mp4 -Frames "100-200" -Confirm
|
||||||
|
```
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
## Synopsis
|
||||||
|
|
||||||
|
This script:
|
||||||
|
|
||||||
|
1. Extracts the video duration using ffprobe
|
||||||
|
2. Parses the first subtitle file to find the last subtitle number
|
||||||
|
3. Adjusts the second subtitle file's timing by adding the video duration
|
||||||
|
4. Renumbers the second subtitle entries to continue from the first file
|
||||||
|
5. Combines both into a single output SRT file
|
||||||
|
|
||||||
|
This is useful when you have two video parts with separate subtitle files and want to create a single combined subtitle file for the full video.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
- **`-Video`** (required): Path to the video file (used to determine duration for offset)
|
||||||
|
- **`-Subtitle1`** (required): Path to the first subtitle file (SRT format)
|
||||||
|
- **`-Subtitle2`** (required): Path to the second subtitle file (SRT format)
|
||||||
|
- **`-Output`** (required): Path to the output combined subtitle file (SRT format)
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
**Timing Adjustment:**
|
||||||
|
|
||||||
|
The second subtitle file is assumed to start at 00:00:00,000 for its own video. The script adds the first video's duration to all timestamps in the second subtitle file.
|
||||||
|
|
||||||
|
Example: If the first video is exactly 3 minutes (00:03:00,000), then a subtitle entry from the second file at 00:00:05,000 becomes 00:03:05,000 in the output.
|
||||||
|
|
||||||
|
**Numbering:**
|
||||||
|
|
||||||
|
Subtitle entries are renumbered sequentially. If the first file ends with subtitle #45, the second file's entries start at #46.
|
||||||
|
|
||||||
|
**Format:**
|
||||||
|
|
||||||
|
All files use SRT (SubRip Subtitle) format:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1
|
||||||
|
00:00:01,000 --> 00:00:04,000
|
||||||
|
First subtitle text
|
||||||
|
|
||||||
|
2
|
||||||
|
00:00:05,000 --> 00:00:08,000
|
||||||
|
Second subtitle text
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
**Basic usage:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-FullSubtitle.ps1 -Video "part1.mp4" -Subtitle1 "part1.srt" -Subtitle2 "part2.srt" -Output "combined.srt"
|
||||||
|
```
|
||||||
|
|
||||||
|
**With absolute paths:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-FullSubtitle.ps1 `
|
||||||
|
-Video "C:\Videos\part1.mp4" `
|
||||||
|
-Subtitle1 "C:\Subtitles\part1.srt" `
|
||||||
|
-Subtitle2 "C:\Subtitles\part2.srt" `
|
||||||
|
-Output "D:\Output\full.srt"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Combine multiple parts:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# First combine part1 and part2
|
||||||
|
.\Create-FullSubtitle.ps1 -Video "part1.mp4" -Subtitle1 "part1.srt" -Subtitle2 "part2.srt" -Output "temp.srt"
|
||||||
|
|
||||||
|
# Then combine result with part3
|
||||||
|
.\Create-FullSubtitle.ps1 -Video "combined_1_2.mp4" -Subtitle1 "temp.srt" -Subtitle2 "part3.srt" -Output "final.srt"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Safe Execution
|
||||||
|
|
||||||
|
This script supports `-WhatIf` and `-Confirm` for safe execution:
|
||||||
|
|
||||||
|
- **`-WhatIf`**: Simulate the operation without actually creating the output file. Shows what would be combined.
|
||||||
|
- **`-Confirm`**: Prompt for confirmation before creating the output directory and writing the output file.
|
||||||
|
|
||||||
|
**Simulate without creating files:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-FullSubtitle.ps1 -Video "video.mp4" -Subtitle1 "sub1.srt" -Subtitle2 "sub2.srt" -Output "out.srt" -WhatIf
|
||||||
|
```
|
||||||
|
|
||||||
|
**Prompt before writing:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-FullSubtitle.ps1 -Video "video.mp4" -Subtitle1 "sub1.srt" -Subtitle2 "sub2.srt" -Output "out.srt" -Confirm
|
||||||
|
```
|
||||||
|
|
||||||
|
## Existing Output
|
||||||
|
|
||||||
|
If the file given with `-Output` already exists, it is **overwritten** without a prompt. The folder is created when it does not exist. With `-WhatIf`, nothing is written.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Requires `ffprobe` in system PATH to extract video duration
|
||||||
|
- All subtitle files must be in SRT format
|
||||||
|
- Output file is written with UTF-8 encoding
|
||||||
|
- The output directory is created if it does not exist
|
||||||
|
- Subtitle timing is precise to the millisecond
|
||||||
@@ -0,0 +1,254 @@
|
|||||||
|
# Create-PillarBox.ps1
|
||||||
|
|
||||||
|
Creates a pillar-boxed (16:9) video file from a source video using ffmpeg, with selectable side-bar effects ranging from solid colors to blurred backgrounds derived from the video itself.
|
||||||
|
|
||||||
|
<video controls width="100%"><source src="../../../images/Create-PillarBox.mp4" type="video/mp4"></video>
|
||||||
|
|
||||||
|
*The video above was generated using the [Compare-Videos.ps1](Compare-Videos.md) script.*
|
||||||
|
|
||||||
|
Each video shows the same source material with a different effect as follows:
|
||||||
|
|
||||||
|
| Left side | Right side |
|
||||||
|
| :-------: | :--------: |
|
||||||
|
| black | standard |
|
||||||
|
| gaussian | dark |
|
||||||
|
|
||||||
|
## Synopsis
|
||||||
|
|
||||||
|
Wraps the source video in a 16:9 frame so it can be played on standard widescreen displays without stretching:
|
||||||
|
|
||||||
|
- **Portrait-like sources** (e.g. 9:16, 1:1) get **vertical** bars on the left and right (true pillar-boxing).
|
||||||
|
- **Sources wider than 16:9** get **horizontal** bars on the top and bottom (letter-boxing).
|
||||||
|
- **Square (1:1) sources with `-Vertical`** are wrapped in a **9:16 portrait** frame instead, with **horizontal** bars on the top and bottom — for vertical platforms such as Shorts, Reels, or TikTok.
|
||||||
|
- The source video is scaled to fit the chosen output frame while preserving its aspect ratio, then centered.
|
||||||
|
- The bars themselves can be solid black/white or filled with a stylised, blurred copy of the video.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
- **`-Video`** (required): Path to input video file (`.mp4` extension added if missing)
|
||||||
|
- **`-Target`** (optional): Path to output video file (`.mp4` extension added if missing). If omitted, defaults to the source filename with the `-Effect` value (lowercased) appended after a dash and saved alongside the source — e.g. `video.mp4` + `-Effect Black` produces `video-black.mp4` next to the source. When `-Vertical` is used, `-vertical` is appended after the effect, just before the extension — e.g. `video-black-vertical.mp4`.
|
||||||
|
- **`-Effect`** (required): Visual treatment of the side bars. One of `black`, `white`, `silver`, `gray`, `charcoal`, `standard`, `dark`, `gaussian`, or `color:RRGGBB` for an arbitrary hex color (case-insensitive on both the keyword and the hex value). See [Effects](#effects) for a detailed description.
|
||||||
|
- **`-Vertical`**: Only valid when the source video is square (1:1). Produces a **9:16 portrait** output with the bars on the top and bottom instead of the default 16:9 landscape output with bars on the sides. All other parameters (`-Effect`, `-Size`, `-FPS`, `-Audio`, `-NoAudio`, `-CRF`) behave identically, just applied to a portrait frame. When `-Target` is omitted, the auto-generated filename ends with `-vertical` (e.g. `video-black-vertical.mp4`). Using it with a non-square source throws an error.
|
||||||
|
- **`-Size`** (optional): Output video size. Possible values: `480p`, `720p`, `1080p`, `HD`, `1440p`, `2K`, `2160p`, `4K`, or custom `width:height`. If not specified, the smallest standard 16:9 size that fully contains the source is chosen automatically. With `-Vertical`, named presets are transposed to portrait (`1080p` → `1080x1920`, `4K` → `2160x3840`); a custom `width:height` is always used exactly as given.
|
||||||
|
- **`-FPS`** (optional): Frames per second for the output video. Uses the input video's FPS if not specified.
|
||||||
|
- **`-NoAudio`**: Exclude audio from the output video. Cannot be combined with `-Audio`.
|
||||||
|
- **`-Audio`** (optional): Path to an alternate audio file to use for the target video. When specified, this audio replaces the source video's audio (re-encoded as AAC 192k, trimmed to the shortest stream).
|
||||||
|
- **`-CRF`** (optional): Constant Rate Factor for video encoding (`0`-`51`, lower is better quality). When specified, overrides both the source video bitrate and the default `-crf 8` fallback.
|
||||||
|
|
||||||
|
## Effects
|
||||||
|
|
||||||
|
The `-Effect` parameter controls what fills the area around the source video inside the 16:9 frame. The eight effects fall into two groups:
|
||||||
|
|
||||||
|
### Solid colors
|
||||||
|
|
||||||
|
These produce flat, uniform bars. They are the cheapest to encode and are best when you want the viewer's eye to ignore the bars completely.
|
||||||
|
|
||||||
|
- **`black`** — Solid black bars (`#000000`). Standard television "matted" look. Maximum contrast against the picture; effectively invisible on OLED/dark-room playback.
|
||||||
|
- **`white`** — Solid white bars (`#FFFFFF`). High-key look suitable for product/light backgrounds, slideshows, and graphic-design contexts.
|
||||||
|
- **`silver`** — Solid silver bars (`#C0C0C0`). Light neutral gray; softer than `white` while staying bright. Useful when pure white feels too aggressive against the picture.
|
||||||
|
- **`gray`** — Solid mid gray bars (`#808080`). 50% luminance neutral; sits between dark and bright content without leaning either way. A safe default for mixed/unknown content brightness.
|
||||||
|
- **`charcoal`** — Solid charcoal bars (`#36454F`). Very dark, slightly blue-tinted gray. Almost as unobtrusive as `black` on OLED but with a more refined look in lit rooms or on LCD screens where pure black can look gray anyway.
|
||||||
|
- **`color:RRGGBB`** — Solid bars in any hex color you specify. The six hex digits are case-insensitive, so `color:FFFF00`, `color:ffff00`, and `color:FfFf00` all produce the same bright yellow bars. Examples: `color:FFFF00` (yellow), `color:1E90FF` (dodger blue), `color:8B0000` (dark red). Use this when none of the named presets match your branding or scene palette.
|
||||||
|
|
||||||
|
### Blurred-background ("ambient") effects
|
||||||
|
|
||||||
|
These three effects all use the same fundamental technique: the source video is duplicated; one copy is **scaled to *fill* the 16:9 frame** (so the picture overflows the visible area in one dimension and is cropped) and then **blurred** to become the background. The original, un-blurred picture is then **centered on top** at its true aspect ratio. The result is that the bars are not empty space — they are a soft, motion-synchronised continuation of the video, similar to the "ambient backlight" treatment used on Apple TV and YouTube.
|
||||||
|
|
||||||
|
The three effects differ only in **how the background is treated** before the original picture is laid on top:
|
||||||
|
|
||||||
|
- **`standard`** — *Soft, neutral, slightly zoomed.*
|
||||||
|
- **Scaling**: the background copy is scaled to roughly **110%** of the output frame and then cropped, giving the bars a subtle "pushed beyond the edges" feel rather than a flat fit. This avoids hard edges where the cropped border would otherwise sit exactly at the frame edge, and adds a barely-noticeable parallax depth.
|
||||||
|
- **Blur**: a fast **box blur** of moderate radius. Box blur is computationally cheap; visually the background becomes recognisable smears of color and motion, but with slight box-shaped artefacts on sharp edges if you look closely.
|
||||||
|
- **Brightness/color**: unchanged. The bars therefore retain the source's full brightness and saturation.
|
||||||
|
- **What you'll see**: side bars filled with soft, recognisable shapes from the picture — sky color, skin tones, scenery — that move and shift in sync with the video. The center picture pops because it is sharp against a softer surround, but the overall image stays bright.
|
||||||
|
- **Best for**: most general-purpose playback. Music videos, vlogs, product demos.
|
||||||
|
|
||||||
|
- **`dark`** — *Same blur as `standard`, but dimmed.*
|
||||||
|
- **Scaling**: background fills the frame exactly (no extra zoom).
|
||||||
|
- **Blur**: same box blur as `standard`.
|
||||||
|
- **Brightness/color**: brightness is reduced (~ -25%) and saturation is reduced to ~80%, producing a darker, more muted background. The center video is **not** affected — only the bars.
|
||||||
|
- **What you'll see**: side bars in the same shapes and motion as `standard`, but visibly darker and less colorful, so the eye is drawn more strongly to the original picture in the middle. Good for cinematic or moody content where the bars should "sink into" the screen.
|
||||||
|
- **Best for**: film clips, dramatic content, dark-room viewing, or any time `standard` feels too bright/distracting.
|
||||||
|
|
||||||
|
- **`gaussian`** — *Smooth, premium-looking blur.*
|
||||||
|
- **Scaling**: background fills the frame exactly (no extra zoom).
|
||||||
|
- **Blur**: a **Gaussian blur** with a large sigma (~30). Gaussian blur is much more computationally expensive than box blur, but it produces a smoother fall-off with no visible artefacts at any zoom level. Sharp features in the original picture become unrecognisable color clouds.
|
||||||
|
- **Brightness/color**: unchanged.
|
||||||
|
- **What you'll see**: side bars that look like a smooth, abstract color wash that clearly comes from the video but where you can no longer make out shapes — only ambient color. Closest to the "broadcast TV" pillar-box look used by streaming services.
|
||||||
|
- **Best for**: professional/broadcast deliverables, finished cuts where rendering time is acceptable, and any content where the `standard` blur looks too "blocky".
|
||||||
|
|
||||||
|
### Quick comparison
|
||||||
|
|
||||||
|
| Effect | Background source | Blur type | Brightness | Use when… |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `black` | none (solid `#000000`) | — | — | You want the bars to disappear visually. |
|
||||||
|
| `white` | none (solid `#FFFFFF`) | — | — | You want a bright, graphic-design feel. |
|
||||||
|
| `silver` | none (solid `#C0C0C0`) | — | — | You want a light bar that's softer than pure white. |
|
||||||
|
| `gray` | none (solid `#808080`) | — | — | Neutral 50% gray that doesn't lean light or dark. |
|
||||||
|
| `charcoal` | none (solid `#36454F`) | — | — | A refined near-black that holds up on LCD/lit rooms. |
|
||||||
|
| `color:RRGGBB` | none (solid custom hex, case-insensitive) | — | — | You need an arbitrary brand or accent color, e.g. `color:FFFF00`. |
|
||||||
|
| `standard` | video, ~110% zoom | box blur (fast) | normal | General use; you want context in the bars. |
|
||||||
|
| `dark` | video | box blur (fast) | dimmed & desaturated | Cinematic look; you want focus on the picture. |
|
||||||
|
| `gaussian` | video | Gaussian (slow, smooth) | normal | Premium/broadcast look; rendering time is OK. |
|
||||||
|
|
||||||
|
## Size selection
|
||||||
|
|
||||||
|
When `-Size` is omitted, the script picks the **smallest standard 16:9 preset that holds the source's bounding box** — i.e. the preset's long side must be at least the source's long side **and** its short side at least the source's short side:
|
||||||
|
|
||||||
|
- A 1080×1920 portrait source → `1920x1080` (1080p) — vertical bars on either side.
|
||||||
|
- A 720×1280 portrait source → `1280x720` (720p) — vertical bars on either side.
|
||||||
|
- A 1080×1080 square source → `1920x1080` (1080p) — vertical bars on either side, source kept at its native 1080×1080.
|
||||||
|
- A 512×512 square source → `1280x720` (720p) — the 480p frame (854×480) is too short to hold 512 px.
|
||||||
|
- A 2560×1080 ultra-wide source → `2560x1440` (1440p) — horizontal bars top and bottom.
|
||||||
|
- A source too large for any preset → an even-rounded custom 16:9 frame that holds the source's bounding box (e.g. 4000×4000 → `7112x4002`).
|
||||||
|
|
||||||
|
Checking both sides matters most for square-ish sources: a 1080×1080 source would fit *inside* a 1280×720 frame only by being shrunk to 720×720, so the next preset up is used instead and no resolution is lost.
|
||||||
|
|
||||||
|
You can always force a specific size with `-Size 1080p`, `-Size 4K`, or a custom `-Size 1920:1080`.
|
||||||
|
|
||||||
|
### With `-Vertical` (square sources)
|
||||||
|
|
||||||
|
The same logic applies to a transposed 9:16 frame:
|
||||||
|
|
||||||
|
- A 1080×1080 square source → `1080x1920` (1080p transposed) — horizontal bars top and bottom, source kept at 1080×1080.
|
||||||
|
- A 512×512 square source → `720x1280` (720p transposed) — horizontal bars top and bottom.
|
||||||
|
- `-Size 1080p` → `1080x1920`, `-Size 720p` → `720x1280`, `-Size 4K` → `2160x3840`.
|
||||||
|
- A custom `-Size 1080:1920` is used verbatim (no transposition).
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
**Auto-named output (no `-Target` specified):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-PillarBox.ps1 -Video video.mp4 -Effect black
|
||||||
|
# Produces video-black.mp4 next to video.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-PillarBox.ps1 -Video vlog.mp4 -Effect Gaussian
|
||||||
|
# Produces vlog-gaussian.mp4 (effect name is always lowercased)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pillar-box a portrait video with black bars at 1080p:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-PillarBox.ps1 -Video portrait.mp4 -Target portrait_16x9.mp4 -Effect black -Size 1080p
|
||||||
|
```
|
||||||
|
|
||||||
|
**Turn a square video into a 9:16 vertical video (bars on top and bottom):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-PillarBox.ps1 -Video square.mp4 -Effect black -Vertical -Size 1080p
|
||||||
|
# Produces square-black-vertical.mp4 at 1080x1920 with black bars above and below the picture
|
||||||
|
```
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-PillarBox.ps1 -Video square.mp4 -Target reel.mp4 -Effect gaussian -Vertical
|
||||||
|
# Blurred ambient bars on top and bottom; size derived from the source (1080x1080 -> 1080x1920)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Auto-size, white bars:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-PillarBox.ps1 -Video clip.mp4 -Target clip_white.mp4 -Effect white
|
||||||
|
# Output size derived from source dimensions
|
||||||
|
```
|
||||||
|
|
||||||
|
**Custom yellow brand color (auto-named output):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-PillarBox.ps1 -Video clip.mp4 -Effect color:FFFF00
|
||||||
|
# Produces clip-color-ffff00.mp4 with solid yellow side bars.
|
||||||
|
# The hex value is case-insensitive: -Effect color:ffff00 works identically.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Soft ambient blur background (general purpose):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-PillarBox.ps1 -Video vlog.mp4 -Target vlog_16x9.mp4 -Effect standard -Size 1080p
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cinematic darkened blur:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-PillarBox.ps1 -Video scene.mp4 -Target scene_16x9.mp4 -Effect dark -Size 1080p
|
||||||
|
```
|
||||||
|
|
||||||
|
**Broadcast-quality Gaussian blur:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-PillarBox.ps1 -Video music_video.mp4 -Target broadcast.mp4 -Effect gaussian -Size 1080p
|
||||||
|
```
|
||||||
|
|
||||||
|
**Force 4K output:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-PillarBox.ps1 -Video clip.mp4 -Target clip_4k.mp4 -Effect gaussian -Size 4K
|
||||||
|
```
|
||||||
|
|
||||||
|
**Custom resolution:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-PillarBox.ps1 -Video clip.mp4 -Target clip.mp4 -Effect black -Size 1920:1080
|
||||||
|
```
|
||||||
|
|
||||||
|
**Replace audio with a soundtrack:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-PillarBox.ps1 -Video video.mp4 -Target output.mp4 -Effect standard -Audio soundtrack.mp3
|
||||||
|
# Source audio is dropped; soundtrack.mp3 replaces it (AAC 192k, trimmed to shortest stream)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Strip audio entirely:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-PillarBox.ps1 -Video video.mp4 -Target silent.mp4 -Effect black -NoAudio
|
||||||
|
```
|
||||||
|
|
||||||
|
**Force a specific FPS:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-PillarBox.ps1 -Video video.mp4 -Target output.mp4 -Effect standard -FPS 30
|
||||||
|
```
|
||||||
|
|
||||||
|
**Override quality with CRF:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-PillarBox.ps1 -Video video.mp4 -Target output.mp4 -Effect gaussian -CRF 18
|
||||||
|
# CRF 18 instead of default CRF 8 / source bitrate
|
||||||
|
```
|
||||||
|
|
||||||
|
## Safe Execution
|
||||||
|
|
||||||
|
This script supports `-WhatIf` and `-Confirm` for safe execution:
|
||||||
|
|
||||||
|
- **`-WhatIf`**: Simulate the operation without creating files. Prints the full ffmpeg command that would have been run.
|
||||||
|
- **`-Confirm`**: Prompt for confirmation before each state-changing operation.
|
||||||
|
|
||||||
|
**Simulate without making changes:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-PillarBox.ps1 -Video video.mp4 -Target output.mp4 -Effect standard -WhatIf
|
||||||
|
```
|
||||||
|
|
||||||
|
**Prompt before creating files:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-PillarBox.ps1 -Video video.mp4 -Target output.mp4 -Effect gaussian -Confirm
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quality Preservation
|
||||||
|
|
||||||
|
- **Codec**: output is encoded with `libx264` (H.264) using `yuv420p` and the `slow` preset.
|
||||||
|
- **Bitrate**: priority is `-CRF` (when specified) → source video bitrate (via `-b:v`) → fallback `-crf 8` (near-lossless).
|
||||||
|
- **Audio**: when present and not replaced/excluded, audio is **stream-copied** with no quality loss. When `-Audio` is used, the replacement audio is re-encoded to AAC 192k.
|
||||||
|
- **Aspect ratio**: source content is never stretched — only scaled proportionally and padded.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Requires `ffmpeg` and `ffprobe` on the Windows `PATH`.
|
||||||
|
- Output dimensions are forced to even pixels for `yuv420p` compatibility.
|
||||||
|
- The `gaussian` effect is noticeably slower to encode than `standard`/`dark`; expect a real-time-or-slower rendering speed at 1080p on typical CPUs.
|
||||||
|
- The `standard` and `dark` effects use box blur for speed; if you see visible block-like edges in the bars, switch to `gaussian` for a smoother result.
|
||||||
|
- When the source aspect ratio is already 16:9, all five effects produce a visually identical output (the source fills the frame; the bars have zero size).
|
||||||
|
- `-Vertical` is rejected for non-square sources: a 16:9 or 9:16 source already has an unambiguous bar orientation, so the switch would only be a source of confusion.
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Create-Scenes.ps1
|
||||||
|
|
||||||
|
Detects scene changes in a video with ffmpeg and extracts each scene as a separate clip file. Each scene is created by calling [Create-Clip.ps1](Create-Clip.md) with the scene's frame range, which in turn calls [Clone-Video.ps1](Clone-Video.md).
|
||||||
|
|
||||||
|
The clips are saved next to the source video, named from the source filename and the scene number: `P01.mp4` produces `P01-Scene-01.mp4`, `P01-Scene-02.mp4`, and so on. A `.skip` marker stays last: `P01.skip.mp4` produces `P01-Scene-01.skip.mp4`.
|
||||||
|
|
||||||
|
## 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`.
|
||||||
|
- **`-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.
|
||||||
|
|
||||||
|
Refer to [Clone-Video.ps1](Clone-Video.md) for a full description of the encoding parameters.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- `ffmpeg` and `ffprobe` must be in the `PATH`.
|
||||||
|
- `Create-Clip.ps1` must be in the same directory as `Create-Scenes.ps1`.
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
1. **Detection**: ffmpeg runs the `scene` score filter with the `-Scene` 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`.
|
||||||
|
5. **Cleanup**: unless `-List` is given, files from a previous run are deleted (see [Replacing Existing Clips](#replacing-existing-clips)).
|
||||||
|
6. **Clip creation**: one `Create-Clip.ps1` call per scene, with frames `start-end` and the scene suffix.
|
||||||
|
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
`StartTime` and `EndTime` are in seconds, computed as frame index divided by frame rate.
|
||||||
|
|
||||||
|
## 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`:
|
||||||
|
|
||||||
|
- `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.
|
||||||
|
|
||||||
|
## 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:
|
||||||
|
|
||||||
|
| Placeholder | Scene 1 | Scene 12 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `%d` | `1` | `12` |
|
||||||
|
| `%02d` | `01` | `12` |
|
||||||
|
| `%003d` | `001` | `012` |
|
||||||
|
| `%03d` | `001` | `012` |
|
||||||
|
|
||||||
|
Other text in the suffix is used as written, so `-Suffix "-Shot-%03d"` produces `P01-Shot-001.mp4`. A suffix without a placeholder, such as `-Suffix "-Scene"`, is rejected. A literal `%%` is not supported.
|
||||||
|
|
||||||
|
## Replacing Existing Clips
|
||||||
|
|
||||||
|
Before the new clips are created, the script deletes earlier clips of the same source. A file is deleted when all of these match:
|
||||||
|
|
||||||
|
- It is in the same directory as the source video.
|
||||||
|
- Its name is the source's base name, then the suffix pattern (any number in place of the placeholder), then `.skip` only if the source had one, then the source's extension.
|
||||||
|
- It is not the source video itself.
|
||||||
|
|
||||||
|
Matching is case-insensitive. Files that do not match are never touched. Existing clips are always replaced, so you do not need to delete them first.
|
||||||
|
|
||||||
|
| Source | Matches | Does not match |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `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.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
**List the scenes without creating anything:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Scenes.ps1 -Video P01.mp4 -Scene 0.4 -List
|
||||||
|
```
|
||||||
|
|
||||||
|
**Create one clip per scene:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Scenes.ps1 -Video P01.mp4
|
||||||
|
# Creates: P01-Scene-01.mp4, P01-Scene-02.mp4, ...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Scale and remove audio from every clip:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Scenes.ps1 -Video P01 -Scene 0.3 -Size 720p -NoAudio
|
||||||
|
```
|
||||||
|
|
||||||
|
**Custom suffix:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Scenes.ps1 -Video P01.mp4 -Suffix "-Shot-%03d"
|
||||||
|
# Creates: P01-Shot-001.mp4, P01-Shot-002.mp4, ...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Source with a `.skip` marker:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Scenes.ps1 -Video P01.skip.mp4
|
||||||
|
# Creates: P01-Scene-01.skip.mp4, P01-Scene-02.skip.mp4, ...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Higher-quality clips:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Scenes.ps1 -Video P01.mp4 -CRF 18
|
||||||
|
```
|
||||||
|
|
||||||
|
## Safe Execution
|
||||||
|
|
||||||
|
This script supports `-WhatIf` and `-Confirm`. Both apply to the deletion of old clips and to the creation of new ones, and are forwarded to `Create-Clip.ps1`.
|
||||||
|
|
||||||
|
- **`-WhatIf`**: Shows the scene list, the files that would be deleted, and the clips that would be created. Nothing is changed on disk.
|
||||||
|
- **`-Confirm`**: Prompts before each deletion and each clip creation.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Scenes.ps1 -Video P01.mp4 -WhatIf
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- 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.
|
||||||
|
- 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.
|
||||||
@@ -0,0 +1,346 @@
|
|||||||
|
# Create-ShortClip.ps1
|
||||||
|
|
||||||
|
Creates a short clip video file from a 16:9 video file with dynamic cropping, padding, and optional overlay.
|
||||||
|
|
||||||
|
## Synopsis
|
||||||
|
|
||||||
|
Transforms landscape videos into portrait (9:16) or square (1:1) format clips by:
|
||||||
|
|
||||||
|
1. Extracting frames with optional vertical padding
|
||||||
|
2. Applying horizontal cropping with smooth transitions between keyframes
|
||||||
|
3. Optionally overlaying a PNG image with transparency
|
||||||
|
4. Encoding the final video with preserved quality settings
|
||||||
|
|
||||||
|
The output filename is automatically generated by appending `-ShortClip` before the file extension.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
- **`-Video`** (required): Path to input video file (absolute or relative)
|
||||||
|
- **`-Plan`** (required): Path to INI plan file defining clip specifications and cropping behavior
|
||||||
|
- **`-Images`** (optional): Path to output PNG images file pattern. If not provided, frames are saved in subdirectory 'Frames' with video filename and 4-digit frame numbers
|
||||||
|
- **`-Keep`**: Preserve temporary and frames directories after processing
|
||||||
|
- **`-Render`**: Skip frame extraction/cropping and only create video from existing frames
|
||||||
|
- **`-Color`** (optional): Override INI file Color value for padding color name or RGB value (default: `black`)
|
||||||
|
- Named colors: `black`, `white`, `red`, `blue`, etc.
|
||||||
|
- Hex format: `#RRGGBB` (e.g., `#1a1a1a`)
|
||||||
|
- FFmpeg format: `0xRRGGBB`
|
||||||
|
- **`-Overlay`** (optional): Override INI file Overlay value for path to PNG file with transparency to overlay on each cropped frame
|
||||||
|
- **`-Smoothing`** (optional): Override INI file Smoothing value (`None`, `Up`, `Center`, `Down`)
|
||||||
|
- **`-Speed`** (optional): Override INI file Speed value (minimum: 1)
|
||||||
|
- **`-WhatIf`**: Simulate the operation without actually creating files. Shows what would be done.
|
||||||
|
- **`-Confirm`**: Prompt for confirmation before each state-changing operation (frame extraction, cropping, video creation).
|
||||||
|
|
||||||
|
## Plan File Format (INI)
|
||||||
|
|
||||||
|
### [Clip] Section
|
||||||
|
|
||||||
|
Defines output video specifications:
|
||||||
|
|
||||||
|
- **`Format`**: Output aspect ratio (default: `Portrait`)
|
||||||
|
- `Portrait`: 9:16 aspect ratio
|
||||||
|
- `Square`: 1:1 aspect ratio
|
||||||
|
- **`Height`**: Vertical height in pixels (default: `1920`). Width is computed based on format
|
||||||
|
- **`Padding`**: Vertical padding position when input height < output height (default: `Center`)
|
||||||
|
- `Top`: All padding at top
|
||||||
|
- `Bottom`: All padding at bottom
|
||||||
|
- `Center`: Padding split evenly top and bottom
|
||||||
|
- `None`: No padding; scale video to fill output height and crop horizontally to match format width
|
||||||
|
- **'Color'**: Padding color name or RGB value (default: `black`)
|
||||||
|
- Named colors: `black`, `white`, `red`, `blue`, etc.
|
||||||
|
- Hex format: `#RRGGBB` (e.g., `#1a1a1a`)
|
||||||
|
- FFmpeg format: `0xRRGGBB`
|
||||||
|
- **`Overlay`**: Path to PNG file with transparency to overlay on each cropped frame
|
||||||
|
- **`Audio`**: Copy audio from input video (default: `Yes`)
|
||||||
|
- `Yes`: Include audio
|
||||||
|
- `No`: Video only
|
||||||
|
- **`Position`**: Reference point for numeric cropping offset values (default: `Center`)
|
||||||
|
- `Left`: Numeric values represent absolute pixel offset from left edge
|
||||||
|
- `Center`: Numeric values represent pixel offset from center (value - output_width/2)
|
||||||
|
- `Right`: Numeric values represent pixel offset from right edge (value - output_width)
|
||||||
|
- **`Smoothing`**: Mode for adjusting cropping transition length to control speed (default: `None`)
|
||||||
|
- `None`: No smoothing, use calculated interpolation as-is
|
||||||
|
- `Up`: Extend transition by incrementing upper frame until speed limit is met
|
||||||
|
- `Center`: Extend transition by adjusting frame closest to center until speed limit is met
|
||||||
|
- `Down`: Extend transition by decrementing lower frame until speed limit is met
|
||||||
|
- **`Speed`**: Maximum cropping transition speed in pixels per frame (default: `10`, minimum: `1`)
|
||||||
|
- Controls how fast the crop position can change between frames
|
||||||
|
- If interpolated speed exceeds this value, smoothing extends the transition
|
||||||
|
|
||||||
|
### [Cropping] Section
|
||||||
|
|
||||||
|
Defines horizontal cropping keyframes with automatic interpolation:
|
||||||
|
|
||||||
|
**Format:** `frame_number=position`
|
||||||
|
|
||||||
|
**Position values:**
|
||||||
|
|
||||||
|
Each keyframe value is resolved to a **Left offset**: the X coordinate (in input-frame pixels) of the left edge of the crop window. The crop window has width `output_width` (or the intermediate width when downscaling).
|
||||||
|
|
||||||
|
- `Left`: Crop from left edge (Left offset = 0)
|
||||||
|
- `Center`: Crop from center (Left offset = (input_width - output_width) / 2)
|
||||||
|
- `Right`: Crop from right edge (Left offset = input_width - output_width)
|
||||||
|
- `number`: Absolute pixel offset, interpreted according to the `Position` setting in the `[Clip]` section (see `Position`). Result is clamped to `[0, input_width - output_width]`.
|
||||||
|
|
||||||
|
From the Left offset, the script also derives:
|
||||||
|
|
||||||
|
- **Center offset** = Left offset + output_width / 2 (X coordinate of the crop window's center)
|
||||||
|
- **Right offset** = Left offset + output_width (X coordinate of the crop window's right edge)
|
||||||
|
|
||||||
|
All three values are printed in the cropping offsets table at runtime to help diagnose framing.
|
||||||
|
|
||||||
|
**Behavior:**
|
||||||
|
|
||||||
|
- Frame numbers must be in ascending order
|
||||||
|
- Intermediate frames are automatically interpolated with smooth transitions
|
||||||
|
- Frames after the last keyframe use the last defined offset
|
||||||
|
- If section is empty or missing, defaults to `0=Center`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Cropping]
|
||||||
|
0=Left
|
||||||
|
84=Left
|
||||||
|
85=Right
|
||||||
|
140=Right
|
||||||
|
159=Left
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates:
|
||||||
|
|
||||||
|
- Frames 0-84: Crop from left
|
||||||
|
- Frame 84→85: Quick transition from left to right
|
||||||
|
- Frames 85-140: Crop from right
|
||||||
|
- Frames 140-159: Smooth pan from right to left
|
||||||
|
- Frames 159+: Crop from left
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
**Basic portrait clip:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-ShortClip.ps1 -Video input.mp4 -Plan plan.ini
|
||||||
|
# Creates: input-ShortClip.mp4 (9:16 format)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Square format with white padding:**
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# plan.ini
|
||||||
|
[Clip]
|
||||||
|
Format=Square
|
||||||
|
Height=1920
|
||||||
|
Padding=Center
|
||||||
|
Audio=Yes
|
||||||
|
[Cropping]
|
||||||
|
0=Center
|
||||||
|
```
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-ShortClip.ps1 -Video video.mp4 -Plan plan.ini -Color white
|
||||||
|
```
|
||||||
|
|
||||||
|
**With overlay graphic:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-ShortClip.ps1 -Video video.mp4 -Plan plan.ini -Overlay border.png
|
||||||
|
# Applies border.png (with transparency) over each cropped frame
|
||||||
|
```
|
||||||
|
|
||||||
|
**No padding (scale to fill height):**
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# plan.ini
|
||||||
|
[Clip]
|
||||||
|
Format=Portrait
|
||||||
|
Height=1920
|
||||||
|
Padding=None
|
||||||
|
```
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-ShortClip.ps1 -Video video.mp4 -Plan plan.ini
|
||||||
|
# Scales video to fill 1920px height, crops horizontally to 1080px width
|
||||||
|
# No vertical padding bars added
|
||||||
|
```
|
||||||
|
|
||||||
|
**Keep frames for inspection:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-ShortClip.ps1 -Video video.mp4 -Plan plan.ini -Keep
|
||||||
|
# Preserves Frames/ directory for review
|
||||||
|
```
|
||||||
|
|
||||||
|
**Re-render with different encoding:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# First run: extract and crop frames
|
||||||
|
.\Create-ShortClip.ps1 -Video video.mp4 -Plan plan.ini -Keep
|
||||||
|
|
||||||
|
# Modify encoding settings in script, then re-render
|
||||||
|
.\Create-ShortClip.ps1 -Video video.mp4 -Plan plan.ini -Render
|
||||||
|
# Skips extraction/cropping, only creates video from existing frames
|
||||||
|
```
|
||||||
|
|
||||||
|
**Custom output location:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-ShortClip.ps1 -Video video.mp4 -Plan plan.ini -Images "C:\Output\frames\frame-%04d.png"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Custom padding color:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Dark gray padding
|
||||||
|
.\Create-ShortClip.ps1 -Video video.mp4 -Plan plan.ini -Color #1a1a1a
|
||||||
|
|
||||||
|
# Navy blue padding
|
||||||
|
.\Create-ShortClip.ps1 -Video video.mp4 -Plan plan.ini -Color navy
|
||||||
|
```
|
||||||
|
|
||||||
|
**Position parameter with numeric values:**
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# plan.ini - Input: 1920x1088, Output: 1080x1920
|
||||||
|
[Clip]
|
||||||
|
Format=Portrait
|
||||||
|
Position=Center
|
||||||
|
[Cropping]
|
||||||
|
0=960
|
||||||
|
```
|
||||||
|
|
||||||
|
With `Position=Center` and output width 1080:
|
||||||
|
|
||||||
|
- Numeric value 960 is adjusted: 960 - (1080/2) = 960 - 540 = 420
|
||||||
|
- Final offset: 420 pixels from left edge (centered in 1920px input)
|
||||||
|
|
||||||
|
With `Position=Left`:
|
||||||
|
|
||||||
|
- Numeric value 960 is used directly: 960 pixels from left edge
|
||||||
|
|
||||||
|
With `Position=Right` and output width 1080:
|
||||||
|
|
||||||
|
- Numeric value 960 is adjusted: 960 - 1080 = -120 → clamped to 0
|
||||||
|
|
||||||
|
**Simulate without making changes:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-ShortClip.ps1 -Video video.mp4 -Plan plan.ini -WhatIf
|
||||||
|
# Displays what would be done (cropping offsets, frame extraction, video creation) without actually processing
|
||||||
|
```
|
||||||
|
|
||||||
|
**Prompt before each operation:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-ShortClip.ps1 -Video video.mp4 -Plan plan.ini -Confirm
|
||||||
|
# Will prompt for confirmation before frame extraction, each cropping operation, and video creation
|
||||||
|
```
|
||||||
|
|
||||||
|
**Smoothing fast transitions:**
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# plan.ini - Control transition speed
|
||||||
|
[Clip]
|
||||||
|
Format=Portrait
|
||||||
|
Smoothing=Center
|
||||||
|
Speed=5
|
||||||
|
[Cropping]
|
||||||
|
0=Left
|
||||||
|
10=Right
|
||||||
|
```
|
||||||
|
|
||||||
|
Without smoothing, frames 0-10 would transition at 84 pixels/frame (too fast).
|
||||||
|
With `Smoothing=Center` and `Speed=5`, the transition extends to more frames, keeping speed ≤ 5 pixels/frame.
|
||||||
|
|
||||||
|
**Override INI settings via command-line:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Override smoothing settings from command line
|
||||||
|
.\Create-ShortClip.ps1 -Video video.mp4 -Plan plan.ini -Smoothing Up -Speed 3
|
||||||
|
# Uses Up smoothing and speed limit of 3, ignoring INI file values
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cropping Interpolation Example
|
||||||
|
|
||||||
|
Input: 1920x1088 video → Output: 1080x1920 (Portrait)
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Cropping]
|
||||||
|
0=Left
|
||||||
|
10=200
|
||||||
|
50=Center
|
||||||
|
91=Right
|
||||||
|
```
|
||||||
|
|
||||||
|
**Calculated Left offsets:**
|
||||||
|
|
||||||
|
- **Frame 0**: 0 (Left)
|
||||||
|
- **Frames 1-9**: Linear interpolation from 0 to 200
|
||||||
|
- Frame 1: 20, Frame 2: 40, ..., Frame 9: 180
|
||||||
|
- **Frame 10**: 200
|
||||||
|
- **Frames 11-49**: Linear interpolation from 200 to 420 (Center = (1920-1080)/2)
|
||||||
|
- Frame 11: 194.5, Frame 12: 189, ..., Frame 49: 425.5
|
||||||
|
- **Frame 50**: 420 (Center)
|
||||||
|
- **Frames 51-90**: Linear interpolation from 420 to 840 (Right = 1920-1080)
|
||||||
|
- Frame 51: 430.5, Frame 52: 441, ..., Frame 90: 829.5
|
||||||
|
- **Frame 91**: 840 (Right)
|
||||||
|
- **Frames 92+**: 840 (last known value)
|
||||||
|
|
||||||
|
All fractional values are rounded to nearest pixel.
|
||||||
|
|
||||||
|
**Runtime output** prints Left, Center (= Left + 540), and Right (= Left + 1080) offsets only at frames where the Left offset changes:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Frame Left Center Right
|
||||||
|
----------------------------------------
|
||||||
|
0 0 540 1080
|
||||||
|
1 20 560 1100
|
||||||
|
2 40 580 1120
|
||||||
|
...
|
||||||
|
10 200 740 1280
|
||||||
|
...
|
||||||
|
50 420 960 1500
|
||||||
|
...
|
||||||
|
91 840 1380 1920
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quality Preservation
|
||||||
|
|
||||||
|
The script automatically preserves video quality from the input:
|
||||||
|
|
||||||
|
- Detects and uses same codec (H.264 → libx264, H.265 → libx265)
|
||||||
|
- Preserves pixel format
|
||||||
|
- Uses input bitrate when available
|
||||||
|
- Falls back to CRF 8 (near-lossless) for VBR/CRF-encoded inputs
|
||||||
|
- Audio encoded as AAC at 192k bitrate
|
||||||
|
|
||||||
|
## Processing Workflow
|
||||||
|
|
||||||
|
1. **Validation**: Check video and plan file existence
|
||||||
|
2. **Analysis**: Extract video metadata (dimensions, frame count, FPS, codec)
|
||||||
|
3. **Offset Calculation**: Compute per-frame cropping offsets with interpolation and optional smoothing
|
||||||
|
4. **Frame Extraction**: Extract all frames with vertical padding applied
|
||||||
|
5. **Cropping**: Apply horizontal crop to each frame based on computed offsets
|
||||||
|
6. **Overlay** (optional): Apply PNG overlay during cropping step
|
||||||
|
7. **Encoding**: Create output video from processed frames
|
||||||
|
8. **Cleanup**: Remove temporary directories (unless `-Keep` specified)
|
||||||
|
|
||||||
|
> With `-WhatIf`, the workflow shows all planned operations without executing them.
|
||||||
|
|
||||||
|
## Existing Files and Folders
|
||||||
|
|
||||||
|
- **Default `-Images`** (the `Frames` folder next to the video): files directly in `Frames` are deleted before extraction. Subfolders are not touched. With `-Render`, nothing is deleted, because the existing frames are the input.
|
||||||
|
- **Explicit `-Images`**: existing frames that match the `-Images` pattern are deleted before extraction. The `Frames` folder is not used or deleted.
|
||||||
|
- **Output video**: an existing file at the output path is overwritten.
|
||||||
|
- **Temporary extraction directory** (in the system temporary folder): always removed when the run ends.
|
||||||
|
- **`Frames` folder** (default `-Images` only): removed after the video is created, unless `-Keep` is given.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Requires `ffmpeg` and `ffprobe` in system PATH
|
||||||
|
- Frame numbers in INI file are 0-based
|
||||||
|
- Overlay PNG should match output dimensions (width × height)
|
||||||
|
- Transparent areas in overlay allow video content to show through
|
||||||
|
- Use `-Render` to quickly re-encode with different settings without reprocessing frames
|
||||||
|
- Use `-WhatIf` to preview the full workflow (cropping offsets, frame operations, video creation) before committing to a full run
|
||||||
|
- Use `-Confirm` to be prompted before each state-changing operation
|
||||||
|
- `Smoothing` only takes effect when the interpolated speed between two keyframes exceeds the `Speed` limit; otherwise offsets are used unchanged
|
||||||
@@ -0,0 +1,262 @@
|
|||||||
|
# Create-Turnaround.ps1
|
||||||
|
|
||||||
|
Combines a sequence of images into a single **turnaround** (character reference sheet) image using ffmpeg.
|
||||||
|
|
||||||
|
A turnaround image shows the same subject from several angles — typically front, 3/4, profile and back — side by side at a consistent scale, in one wide image. It is the usual input for keeping a character consistent across image and video generation models.
|
||||||
|
|
||||||
|
## Synopsis
|
||||||
|
|
||||||
|
All images found at `-Source` are placed side by side, left to right, in the order given by a numeric-aware sort of their file names. `-Target` is optional: without it the sheet is saved as a JPEG named after the source directory, in the current directory. Every image keeps its original pixels: nothing is scaled, stretched or cropped. Images smaller than the largest one are centred in their cell and padded with `-Background` (white by default), so a set of slightly different sizes still produces a clean, aligned sheet.
|
||||||
|
|
||||||
|
Supports PNG, JPEG, WEBP, BMP, AVIF and TIFF for both input and output.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
- **`-Source`** (required): Directory containing the images, a file pattern (e.g. `Img*.jpg`), or a single image file. Can be absolute or relative to the current directory. Non-recursive; only supported image extensions are considered.
|
||||||
|
- **`-Target`** (optional): Path to the output turnaround image. Can be absolute or relative to the current directory. The extension selects the output format; `.png` is appended when the extension is missing or unrecognised. When omitted, the name is derived from the source — see [Default Target Name](#default-target-name).
|
||||||
|
- **`-Rows`** (optional): Number of rows to arrange the images in. Defaults to `1` (a single horizontal row). When only `-Rows` is given, the column count is computed as `ceil(imageCount / Rows)`.
|
||||||
|
- **`-Columns`** (optional): Number of columns to arrange the images in. When only `-Columns` is given, the row count is computed as `ceil(imageCount / Columns)`. When both are given, `Rows × Columns` must be at least the number of images.
|
||||||
|
- **`-Background`** (optional): Colour used to pad images smaller than the largest one, and to fill the unused cells of an incomplete last row. Defaults to `white`. Accepts any ffmpeg colour: a name (`white`, `black`, `silver`), `0xRRGGBB`, or `name@alpha` (`white@0` for transparent).
|
||||||
|
- **`-Quality`** (optional): Quality factor for the lossy output formats (jpeg, webp, avif). **Lower values mean better quality** in all three, see [Quality and Compression](#quality-and-compression). Ignored (with a warning) for png, bmp and tiff.
|
||||||
|
- **`-Compression`** (optional): Compression level for png (`0`-`9`) and webp (`0`-`6`); higher means smaller files but slower encoding. Ignored (with a warning) for jpeg, avif, bmp and tiff.
|
||||||
|
|
||||||
|
## Default Target Name
|
||||||
|
|
||||||
|
When `-Target` is omitted, the script writes a **JPEG named after the last directory member of the source**, into the **current directory**:
|
||||||
|
|
||||||
|
| `-Source` | Name taken from | Result (run from `C:\work`) |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| A directory, e.g. `C:\renders\CharacterA` | The last member of that directory's full path | `C:\work\CharacterA.jpg` |
|
||||||
|
| A file pattern, e.g. `Img*.jpg` | The last member of the **current** directory's full path | `C:\work\work.jpg` |
|
||||||
|
| A single image file | The last member of the current directory's full path | `C:\work\work.jpg` |
|
||||||
|
|
||||||
|
Relative sources are resolved to a full path first, so `-Source .` run from `C:\renders\CharacterA` produces `CharacterA.jpg` in that same directory. Trailing separators are ignored (`C:\renders\CharacterA\` behaves identically).
|
||||||
|
|
||||||
|
Since the derived name always ends in `.jpg`, the output uses the JPEG encoder and its default `-q:v 2`; add `-Quality` to change it, or pass `-Target` explicitly for a different format.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
PS C:\work> .\Create-Turnaround.ps1 -Source C:\renders\CharacterA
|
||||||
|
No -Target specified; using C:\work\CharacterA.jpg
|
||||||
|
```
|
||||||
|
|
||||||
|
### Drive-root error
|
||||||
|
|
||||||
|
A path with no directory member cannot name the file, so a drive root is rejected:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
PS C:\work> .\Create-Turnaround.ps1 -Source "C:\"
|
||||||
|
Cannot derive a target file name from 'C:\': the path is a drive root with no directory member. Specify -Target explicitly.
|
||||||
|
```
|
||||||
|
|
||||||
|
The same applies when `-Source` is a file pattern and the current directory is a drive root (e.g. running `Create-Turnaround.ps1 -Source *.png` from `C:\`). Supply `-Target` explicitly in that case.
|
||||||
|
|
||||||
|
## Image Order
|
||||||
|
|
||||||
|
File names are sorted with a **numeric-aware (natural) sort**, so digit runs compare as numbers:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Img1.png, Img2.png, Img10.png (not Img1, Img10, Img2)
|
||||||
|
Frame-00001.png ... Frame-00012.png
|
||||||
|
```
|
||||||
|
|
||||||
|
Zero-padded sequence names therefore sort identically to a plain alphabetical sort, while non-padded names (`Pose2`, `Pose10`) still come out in the intended order.
|
||||||
|
|
||||||
|
## Cell Size and Padding
|
||||||
|
|
||||||
|
The cell is as large as the **largest** source image in both dimensions:
|
||||||
|
|
||||||
|
- `cellWidth` = max width of all sources
|
||||||
|
- `cellHeight` = max height of all sources
|
||||||
|
- Output size = `cellWidth × columns` by `cellHeight × rows`
|
||||||
|
|
||||||
|
Each image is centred in its cell. Images that already match the cell size are passed through untouched; smaller ones gain `-Background` margins. No source image is ever resized, so the sheet is lossless with respect to the originals (verified at PSNR = ∞ for PNG in/PNG out).
|
||||||
|
|
||||||
|
When padding is applied, the console reports it:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cell size: 600x768
|
||||||
|
Note: 2 image(s) smaller than the largest image; centred and padded with white.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
### Single row (default)
|
||||||
|
|
||||||
|
Four 512x768 images produce a 2048x768 sheet:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[1][2][3][4]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Grid
|
||||||
|
|
||||||
|
`-Rows` and/or `-Columns` wrap the images into a grid. With 4 images and `-Columns 2` (→ 2 rows), the output is 1024x1536:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[1][2]
|
||||||
|
[3][4]
|
||||||
|
```
|
||||||
|
|
||||||
|
An incomplete last row is **centred** inside the full output width and the remaining space filled with `-Background`. With 4 images and `-Columns 3` (→ 2 rows, 1536x1536):
|
||||||
|
|
||||||
|
```text
|
||||||
|
[1][2][3]
|
||||||
|
[4]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Images | Switches | Rows x Columns | Output (512x768 cells) |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 4 | *(none)* | 1 x 4 | 2048x768 |
|
||||||
|
| 4 | `-Columns 2` | 2 x 2 | 1024x1536 |
|
||||||
|
| 4 | `-Rows 2` | 2 x 2 | 1024x1536 |
|
||||||
|
| 4 | `-Columns 3` | 2 x 3 | 1536x1536 (last row centred) |
|
||||||
|
| 4 | `-Rows 1 -Columns 2` | — | Error: 2 cells is not enough for 4 images |
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
The target extension selects the encoder, with the same mapping and defaults as [Clone-Image.ps1](Clone-Image.md):
|
||||||
|
|
||||||
|
| Extension | Encoder | Default settings | `-Quality` | `-Compression` |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `.png` | `png` | Lossless | — | `-compression_level` (0-9) |
|
||||||
|
| `.jpg` / `.jpeg` | `mjpeg` | `-q:v 2`, `yuvj420p` | `-q:v` (1-31) | — |
|
||||||
|
| `.webp` | `libwebp` | `-quality 90` | `-quality` (inverted) | `-compression_level` (0-6) |
|
||||||
|
| `.avif` | `libaom-av1` | `-still-picture 1`, `-cpu-used 4`, `-crf 25` | `-crf` (0-63) | — |
|
||||||
|
| `.bmp` | `bmp` | Lossless | — | — |
|
||||||
|
| `.tif` / `.tiff` | `tiff` | Lossless | — | — |
|
||||||
|
|
||||||
|
PNG is recommended for a turnaround sheet that will be fed back into a generation model, since it is lossless and preserves transparency.
|
||||||
|
|
||||||
|
## Quality and Compression
|
||||||
|
|
||||||
|
`-Quality` is always interpreted as **lower = better quality**, regardless of format. For webp — whose native `-quality` option means the opposite — the value is inverted internally (`-Quality 20` becomes `-quality 80`), so a single convention holds across jpeg, webp and avif.
|
||||||
|
|
||||||
|
| Format | `-Quality` maps to | Range | Default when omitted |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| jpeg | `-q:v` | 1-31 (lower is better) | `2` |
|
||||||
|
| webp | `-quality` = `100 - Quality` | 0-100 after inversion | `90` (i.e. `-Quality 10`) |
|
||||||
|
| avif | `-crf` | 0-63 (lower is better) | `25` |
|
||||||
|
|
||||||
|
`-Compression` trades encoding time for file size on the two formats that support it, and does not affect image quality (both are lossless/independent of it):
|
||||||
|
|
||||||
|
| Format | `-Compression` maps to | Range |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| png | `-compression_level` | 0-9 (higher = smaller, slower) |
|
||||||
|
| webp | `-compression_level` | 0-6 (higher = smaller, slower) |
|
||||||
|
|
||||||
|
Measured on a 3840x480 sheet:
|
||||||
|
|
||||||
|
| Command | Output size |
|
||||||
|
| --- | --- |
|
||||||
|
| `-Target sheet.jpg` (default) | 73,744 B |
|
||||||
|
| `-Target sheet.jpg -Quality 2` | 73,744 B (identical to the default) |
|
||||||
|
| `-Target sheet.jpg -Quality 28` | 33,349 B |
|
||||||
|
| `-Target sheet.webp -Quality 5` | 30,508 B |
|
||||||
|
| `-Target sheet.webp -Quality 70` | 17,578 B |
|
||||||
|
| `-Target sheet.png -Compression 0` | 7,395,829 B |
|
||||||
|
| `-Target sheet.png -Compression 9` | 10,990 B |
|
||||||
|
|
||||||
|
Passing `-Quality` to a lossless format (or `-Compression` to a format that ignores it) is not an error; the value is dropped and a warning is printed:
|
||||||
|
|
||||||
|
```text
|
||||||
|
WARNING: -Quality is ignored for the lossless png format.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
**No `-Target` — JPEG named after the source directory, written to the current directory:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Turnaround.ps1 -Source C:\renders\CharacterA
|
||||||
|
# Writes .\CharacterA.jpg
|
||||||
|
```
|
||||||
|
|
||||||
|
**No `-Target`, run from inside the frames directory:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cd C:\renders\CharacterA
|
||||||
|
.\Create-Turnaround.ps1 -Source "F*.png"
|
||||||
|
# Writes C:\renders\CharacterA\CharacterA.jpg (name taken from the current directory)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Four frames in a directory into a single-row sheet:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Turnaround.ps1 -Source .\renders -Target turnaround.png
|
||||||
|
```
|
||||||
|
|
||||||
|
**Using a file pattern:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Turnaround.ps1 -Source ".\renders\Img*.jpg" -Target turnaround.png
|
||||||
|
```
|
||||||
|
|
||||||
|
**Extension-less target (`.png` is appended):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Turnaround.ps1 -Source .\renders -Target character-sheet
|
||||||
|
# Writes character-sheet.png
|
||||||
|
```
|
||||||
|
|
||||||
|
**Eight angles as a 2x4 grid:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Turnaround.ps1 -Source .\renders -Target turnaround.png -Rows 2
|
||||||
|
```
|
||||||
|
|
||||||
|
**Three columns, incomplete last row centred:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Turnaround.ps1 -Source .\renders -Target turnaround.png -Columns 3
|
||||||
|
```
|
||||||
|
|
||||||
|
**Transparent background instead of white:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Turnaround.ps1 -Source .\renders -Target turnaround.png -Background "white@0"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Mid-gray background, JPEG output:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Turnaround.ps1 -Source .\renders -Target turnaround.jpg -Background 0x808080
|
||||||
|
```
|
||||||
|
|
||||||
|
**Smaller JPEG sheet (lower quality):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Turnaround.ps1 -Source .\renders -Target turnaround.jpg -Quality 20
|
||||||
|
```
|
||||||
|
|
||||||
|
**Maximum PNG compression (smallest lossless file, slowest encode):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Turnaround.ps1 -Source .\renders -Target turnaround.png -Compression 9
|
||||||
|
```
|
||||||
|
|
||||||
|
**Near-lossless webp with maximum compression effort:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Turnaround.ps1 -Source .\renders -Target turnaround.webp -Quality 1 -Compression 6
|
||||||
|
```
|
||||||
|
|
||||||
|
## Safe Execution
|
||||||
|
|
||||||
|
This script supports `-WhatIf` and `-Confirm` for safe execution:
|
||||||
|
|
||||||
|
- **`-WhatIf`**: Prints the image list, the computed layout and the full ffmpeg command without writing the output file.
|
||||||
|
- **`-Confirm`**: Prompts for confirmation before creating the turnaround image.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-Turnaround.ps1 -Source .\renders -Target turnaround.png -WhatIf
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Requires `ffmpeg` and `ffprobe` in the Windows `PATH`.
|
||||||
|
- The target directory must exist; the script errors out rather than creating it.
|
||||||
|
- Every input is normalised to `rgba` before stacking, so sources may mix formats (e.g. PNG + JPEG + WEBP) and colour models freely.
|
||||||
|
- Source images are never scaled, stretched or cropped — differing sizes are reconciled with padding only.
|
||||||
|
- A single image is valid input and produces a one-cell output (useful to normalise/convert a frame).
|
||||||
|
- Files that are not recognised images are ignored; if none remain, the script errors with the list of supported extensions.
|
||||||
|
- The output file is excluded from its own input list, so writing `-Target` into the `-Source` directory stays idempotent across re-runs (the previous sheet is not stacked into the new one).
|
||||||
+189
@@ -0,0 +1,189 @@
|
|||||||
|
# Crop-Clips.ps1
|
||||||
|
|
||||||
|
Batch crop videos generated by **LTX-2**, **Kling** and **Wan** to standard resolutions using ffmpeg.
|
||||||
|
|
||||||
|
Every non-standard resolution these generators produce is recognised by a single lookup table in the script, so one command handles a folder containing clips from any of them. Resolutions are unique per generator, so there is no ambiguity. Any resolution not listed below is skipped without modification.
|
||||||
|
|
||||||
|
Use `-Size` to ignore the table and convert every video to a specific resolution instead — see [Forcing a Size](#forcing-a-size).
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
- **`-Source`** (optional): Source directory containing video files (default: current directory)
|
||||||
|
- **`-Target`** (optional): Target directory for cropped videos (default: current directory)
|
||||||
|
- **`-Size`** (optional): Output video size. Possible values: `320p`, `384p`, `480p`, `720p`, `900p`, `1080p`, `HD`, `1440p`, `2K`, `2160p`, `4K`, or a custom `width:height`. When specified it **overrides the resolution table** and converts every video to that size — see [Forcing a Size](#forcing-a-size). Without it, the table below is used.
|
||||||
|
|
||||||
|
## Supported Resolutions
|
||||||
|
|
||||||
|
### LTX-2
|
||||||
|
|
||||||
|
**Landscape:**
|
||||||
|
|
||||||
|
- **3744x2112** → **3840x2160**: Scale up to 3840 width, then vertical center crop
|
||||||
|
- **2496x1408** → **2560x1440**: Scale up to 2560 width, then vertical center crop
|
||||||
|
- **2160x1216** → **1920x1080**: Scale down to 1920 width, then vertical center crop
|
||||||
|
- **1920x1088** → **1920x1080**: Center crop (removes 4px from top and bottom)
|
||||||
|
- **1920x1056** → **1920x1080**: Scale up to 1080 height, then horizontal center crop
|
||||||
|
- **1872x1056** → **1920x1080**: Scale up to 1920 width, then vertical center crop
|
||||||
|
- **1600x896** → **1600x900**: Scale to 900 height, then center crop
|
||||||
|
- **1312x704** → **1280x720**: Scale up to 720 height, then horizontal center crop
|
||||||
|
- **1280x704** → **1280x720**: Scale to 720 height, then center crop
|
||||||
|
- **1248x704** → **1280x720**: Scale up to 1280 width, then vertical center crop
|
||||||
|
- **832x448** → **854x480**: Scale to 480 height, then center crop
|
||||||
|
- **640x384** → **684x384**: Scale to 684 width, then center crop
|
||||||
|
- **576x320** → **570x320**: Center crop (removes 3px from left and right)
|
||||||
|
|
||||||
|
**Portrait:**
|
||||||
|
|
||||||
|
- **2112x3744** → **2160x3840**: Scale up to 3840 height, then horizontal center crop
|
||||||
|
- **1408x2496** → **1440x2560**: Scale up to 2560 height, then horizontal center crop
|
||||||
|
- **1216x2160** → **1080x1920**: Scale down to 1920 height, then horizontal center crop
|
||||||
|
- **1088x1920** → **1080x1920**: Center crop (removes 4px from left and right)
|
||||||
|
- **1056x1920** → **1080x1920**: Scale up to 1080 width, then vertical center crop
|
||||||
|
- **1056x1872** → **1080x1920**: Scale up to 1920 height, then horizontal center crop
|
||||||
|
- **896x1600** → **900x1600**: Scale to 900 width, then center crop
|
||||||
|
- **704x1312** → **720x1280**: Scale up to 720 width, then vertical center crop
|
||||||
|
- **704x1280** → **720x1280**: Scale to 720 width, then center crop
|
||||||
|
- **704x1248** → **720x1280**: Scale up to 1280 height, then horizontal center crop
|
||||||
|
- **448x832** → **480x854**: Scale to 480 width, then center crop
|
||||||
|
- **384x640** → **384x684**: Scale to 684 height, then center crop
|
||||||
|
- **320x576** → **320x570**: Center crop (removes 3px from top and bottom)
|
||||||
|
|
||||||
|
### Kling
|
||||||
|
|
||||||
|
- **1928x1072** → **1920x1080**: Scale to 1080 height, then center crop width to 1920
|
||||||
|
- **1920x1076** → **1920x1080**: Scale to 1080 height, then center crop width to 1920
|
||||||
|
- **1920x1072** → **1920x1080**: Scale to 1080 height, then center crop width to 1920
|
||||||
|
- **1916x1080** → **1920x1080**: Already 1080 tall, so the 4 missing columns are padded (centered) instead of rescaling
|
||||||
|
|
||||||
|
### Wan
|
||||||
|
|
||||||
|
- **1928x1076** → **1920x1080**: Scale to 1080 height, then center crop width to 1920
|
||||||
|
- **1926x1076** → **1920x1080**: Scale to 1080 height, then center crop width to 1920
|
||||||
|
- **1284x716** → **1280x720**: Scale to 720 height, then center crop width to 1280
|
||||||
|
|
||||||
|
### Square (1:1)
|
||||||
|
|
||||||
|
- **NxN** → **1080x1080**: Any square resolution other than 1080x1080 is scaled up or down to 1080x1080 (no crop needed)
|
||||||
|
|
||||||
|
## Forcing a Size
|
||||||
|
|
||||||
|
`-Size` bypasses the resolution table entirely. Every video is **cover-scaled** (`force_original_aspect_ratio=increase`, which preserves the pixel aspect ratio and guarantees the frame is fully covered) and then **centre-cropped** to the exact requested size — the same approach as [Clone-Image.ps1](Clone-Image.md). Because the table is not consulted, resolutions that would normally be skipped are converted too.
|
||||||
|
|
||||||
|
### Presets
|
||||||
|
|
||||||
|
Named presets are the landscape targets this script already produces:
|
||||||
|
|
||||||
|
| Preset | Size | Preset | Size |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `320p` | 570x320 | `1080p` / `HD` | 1920x1080 |
|
||||||
|
| `384p` | 684x384 | `1440p` / `2K` | 2560x1440 |
|
||||||
|
| `480p` | 854x480 | `2160p` / `4K` | 3840x2160 |
|
||||||
|
| `720p` | 1280x720 | | |
|
||||||
|
| `900p` | 1600x900 | | |
|
||||||
|
|
||||||
|
Preset names are case-insensitive (`-Size hd` works).
|
||||||
|
|
||||||
|
### Portrait sources
|
||||||
|
|
||||||
|
Presets are **transposed for portrait sources**, so a 9:16 clip stays 9:16 instead of losing most of the frame to a landscape crop:
|
||||||
|
|
||||||
|
| Source | `-Size 1080p` | `-Size 720p` |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 1920x1088 (landscape) | 1920x1080 | 1280x720 |
|
||||||
|
| 640x480 (landscape) | 1920x1080 | 1280x720 |
|
||||||
|
| 512x512 (square) | 1920x1080 | 1280x720 |
|
||||||
|
| 1080x1920 (portrait) | 1080x1920 *(already — skipped)* | 720x1280 |
|
||||||
|
| 720x1280 (portrait) | 1080x1920 | 720x1280 *(already — skipped)* |
|
||||||
|
|
||||||
|
A custom `width:height` is **always used exactly as given** (never transposed), so `-Size 1080:1920` forces portrait output for every video, landscape sources included. Odd custom dimensions are rounded up to the next even number, as `libx264`/`yuv420p` requires even sizes (`-Size 571:321` becomes 572x322).
|
||||||
|
|
||||||
|
### Already-correct videos
|
||||||
|
|
||||||
|
A video that is already exactly the requested size is left untouched — no re-encode, no generation loss:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Skipping: 1920x1080.mp4 (already 1920x1080)
|
||||||
|
```
|
||||||
|
|
||||||
|
An invalid `-Size` value fails immediately, before any file is processed.
|
||||||
|
|
||||||
|
## Behavior
|
||||||
|
|
||||||
|
- Processes all `.mp4` files in source directory
|
||||||
|
- Skips files ending with `.bak.mp4` or `.skip.mp4`
|
||||||
|
- Automatically detects resolution and applies appropriate crop filter (or uses `-Size` when given)
|
||||||
|
- Preserves original quality and audio
|
||||||
|
- **In-place mode** (source and target are the same directory). This is the default when `-Source` and `-Target` are both omitted, because both default to the current directory:
|
||||||
|
- Each original `video.mp4` is copied to `video.bak.mp4`. An existing `.bak.mp4` is replaced.
|
||||||
|
- The cropped result is written to `video.crop.mp4`. Only after that succeeds is the original `video.mp4` deleted and replaced by the cropped file. The `.bak.mp4` backup is kept.
|
||||||
|
- Run with `-WhatIf` first, and do not run it on a folder you have not designated.
|
||||||
|
- **Separate target folder**: the source files are not changed. A file with the same name already in the target folder is deleted and replaced.
|
||||||
|
- If ffmpeg fails, the partial `.crop.mp4` file is deleted and the script stops with an error
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
**Crop videos in current directory:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Crop-Clips.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Crop videos from specific source:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Crop-Clips.ps1 -Source "C:\Videos\Input"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Crop to different target directory:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Crop-Clips.ps1 -Source "C:\Videos\Input" -Target "C:\Videos\Output"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Force every video to 1080p (table ignored):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Crop-Clips.ps1 -Source "C:\Videos\Input" -Size 1080p
|
||||||
|
# Landscape clips -> 1920x1080, portrait clips -> 1080x1920
|
||||||
|
```
|
||||||
|
|
||||||
|
**Force a small preset:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Crop-Clips.ps1 -Size 480p
|
||||||
|
# Landscape clips -> 854x480, portrait clips -> 480x854
|
||||||
|
```
|
||||||
|
|
||||||
|
**Force a custom size (used verbatim, never transposed):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Crop-Clips.ps1 -Size "1080:1920"
|
||||||
|
# Every video becomes 1080x1920, landscape sources included
|
||||||
|
```
|
||||||
|
|
||||||
|
**Skip specific files:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Rename files to skip processing:
|
||||||
|
# video.mp4 -> video.skip.mp4
|
||||||
|
.\Crop-Clips.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Safe Execution
|
||||||
|
|
||||||
|
This script supports `-WhatIf` and `-Confirm` for safe execution:
|
||||||
|
|
||||||
|
- **`-WhatIf`**: Simulate the operation without actually modifying files. Shows what would be cropped.
|
||||||
|
- **`-Confirm`**: Prompt for confirmation before processing each video file.
|
||||||
|
|
||||||
|
**Simulate without making changes:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Crop-Clips.ps1 -Source "C:\Videos\Input" -Target "C:\Videos\Output" -WhatIf
|
||||||
|
```
|
||||||
|
|
||||||
|
**Prompt before each file:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Crop-Clips.ps1 -Source "C:\Videos\Input" -Confirm
|
||||||
|
```
|
||||||
+115
@@ -0,0 +1,115 @@
|
|||||||
|
# Get-Frame.ps1
|
||||||
|
|
||||||
|
Extracts a specific frame of a video file to a PNG image by calling [Get-LastFrame.ps1](Get-LastFrame.md) with an automatically derived output filename. The image path is generated from the `-Video` filename by appending a dash and the resolved frame index zero-padded to at least four digits — for example, `P01.mp4` at frame 10 becomes `P01-0010.png` saved alongside the source video.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
- **`-Video`** (required): Path to the input video file. Can be absolute or relative to current directory.
|
||||||
|
- **`-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)
|
||||||
|
|
||||||
|
## Image Filename Derivation
|
||||||
|
|
||||||
|
The output filename is derived from the resolved (always positive) frame index:
|
||||||
|
|
||||||
|
| Video | `-Frame` | Total frames | Output image |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `P01.mp4` | `10` | any | `P01-0010.png` |
|
||||||
|
| `P01.mp4` | `0` | any | `P01-0000.png` |
|
||||||
|
| `P01.mp4` | `-1` | 300 | `P01-0299.png` |
|
||||||
|
| `P01.mp4` | `-2` | 300 | `P01-0298.png` |
|
||||||
|
| `render/shot.mp4` | `42` | any | `render/shot-0042.png` |
|
||||||
|
| `C:\Videos\take01.mp4` | `100` | any | `C:\Videos\take01-0100.png` |
|
||||||
|
|
||||||
|
Negative indices are resolved to the actual frame index before building the filename, so the output always reflects the exact frame that was extracted.
|
||||||
|
|
||||||
|
## Output Name Digits
|
||||||
|
|
||||||
|
The frame index is padded with zeros to **at least** four digits. Frames `0` to `9999` give exactly four digits (`0010`). Larger numbers are written in full: frame `12345` gives `P01-12345.png`.
|
||||||
|
|
||||||
|
## Existing Files
|
||||||
|
|
||||||
|
An existing PNG with the same name is **replaced**. The image is written by [Get-LastFrame.ps1](Get-LastFrame.md), which overwrites its output.
|
||||||
|
|
||||||
|
## Command Wrapper
|
||||||
|
|
||||||
|
`Get-Frame.cmd` takes the video and the frame as **positional** arguments. It calls `Get-Frame.ps1` found on `PATH`, so the script folder must be on `PATH`. Arguments after the frame are passed to `Get-Frame.ps1` unchanged. Only `-WhatIf` and `-Confirm` are accepted there. Other options are rejected.
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
Get-Frame.cmd "P01.mp4" 10
|
||||||
|
```
|
||||||
|
|
||||||
|
This is the same call as `.\Get-Frame.ps1 -Video P01.mp4 -Frame 10`.
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
- `-Image` is not a parameter of `Get-Frame.ps1`, so it cannot choose the output name. To use a custom name or a numbering pattern, call [Get-LastFrame.ps1](Get-LastFrame.md) with `-Image`:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-LastFrame.ps1 -Video P01.mp4 -Frame 10 -Image "P01-%04d.png"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
**Extract a specific frame:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-Frame.ps1 -Video P01.mp4 -Frame 10
|
||||||
|
# Creates: P01-0010.png
|
||||||
|
```
|
||||||
|
|
||||||
|
**Extract the first frame:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-Frame.ps1 -Video P01.mp4 -Frame 0
|
||||||
|
# Creates: P01-0000.png
|
||||||
|
```
|
||||||
|
|
||||||
|
**Extract the last frame:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-Frame.ps1 -Video P01.mp4 -Frame -1
|
||||||
|
# If video has 300 frames, creates: P01-0299.png
|
||||||
|
```
|
||||||
|
|
||||||
|
**Extract the second-to-last frame:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-Frame.ps1 -Video P01.mp4 -Frame -2
|
||||||
|
# If video has 300 frames, creates: P01-0298.png
|
||||||
|
```
|
||||||
|
|
||||||
|
**Extract the 10th frame from the end:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-Frame.ps1 -Video P01.mp4 -Frame -10
|
||||||
|
# If video has 300 frames, creates: P01-0290.png
|
||||||
|
```
|
||||||
|
|
||||||
|
**Using absolute paths:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-Frame.ps1 -Video "C:\Videos\P01.mp4" -Frame 50
|
||||||
|
# Creates: C:\Videos\P01-0050.png
|
||||||
|
```
|
||||||
|
|
||||||
|
## Safe Execution
|
||||||
|
|
||||||
|
This script supports `-WhatIf` and `-Confirm` for safe execution (forwarded to `Get-LastFrame.ps1`):
|
||||||
|
|
||||||
|
- **`-WhatIf`**: Simulate the operation without extracting the frame. Shows what would be done.
|
||||||
|
- **`-Confirm`**: Prompt for confirmation before extracting the frame.
|
||||||
|
|
||||||
|
**Simulate without extracting:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-Frame.ps1 -Video P01.mp4 -Frame 10 -WhatIf
|
||||||
|
```
|
||||||
|
|
||||||
|
**Prompt before extraction:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-Frame.ps1 -Video P01.mp4 -Frame 10 -Confirm
|
||||||
|
```
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# Get-FramesCount.ps1
|
||||||
|
|
||||||
|
Gets the total frame count of a video file using ffprobe.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
- **`-Video`** (required): Path to input video file
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
**Get frame count:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-FramesCount.ps1 -Video video.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
Output example:
|
||||||
|
|
||||||
|
```text
|
||||||
|
500
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use in scripts:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$frameCount = .\Get-FramesCount.ps1 -Video video.mp4
|
||||||
|
Write-Host "Video has $frameCount frames"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Using absolute paths:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-FramesCount.ps1 -Video "C:\Videos\sample.mp4"
|
||||||
|
```
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
# Get-FramesRange.ps1
|
||||||
|
|
||||||
|
Extracts a specific range of frames from a video file to PNG images.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
- **`-Video`** (required): Path to input video file
|
||||||
|
- **`-Images`** (optional): Path to output PNG images file pattern. Can be absolute or relative to current directory. If not provided, the output files will be in subdirectory 'Frames' and named as the video file with a frame number suffix padded with zeros to 4 digits.
|
||||||
|
- **`-Frames`** (required): Frame range specification (see formats below)
|
||||||
|
|
||||||
|
## Frame Range Formats
|
||||||
|
|
||||||
|
- **`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:
|
||||||
|
- `%` - Extract only the last frame
|
||||||
|
- `10-%` - Extract from frame 10 to the last frame
|
||||||
|
- `-%` - Extract only the first frame (frame 0)
|
||||||
|
- **`all`**: Extract all frames (same as `0-%`)
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
**Extract specific range:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-Frames.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"
|
||||||
|
# Creates: Frames/video_0000.png through Frames/video_0050.png
|
||||||
|
```
|
||||||
|
|
||||||
|
**Extract last 10 frames:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-Frames.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 "%"
|
||||||
|
# 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-%"
|
||||||
|
# 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"
|
||||||
|
# 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"
|
||||||
|
# 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"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Frame numbers in output filenames reflect the actual video frame index (0-based).
|
||||||
|
|
||||||
|
## Existing Files
|
||||||
|
|
||||||
|
An existing PNG with the same name is **replaced**. Use `-WhatIf` to check the output names first.
|
||||||
|
|
||||||
|
## Safe Execution
|
||||||
|
|
||||||
|
This script supports `-WhatIf` and `-Confirm` for safe execution:
|
||||||
|
|
||||||
|
- **`-WhatIf`**: Simulate the operation without actually extracting frames. Shows which frames would be extracted.
|
||||||
|
- **`-Confirm`**: Prompt for confirmation before extracting each frame.
|
||||||
|
|
||||||
|
**Simulate without extracting:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-Frames.ps1 -Video video.mp4 -Frames "100-200" -WhatIf
|
||||||
|
```
|
||||||
|
|
||||||
|
**Prompt before each frame:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-Frames.ps1 -Video video.mp4 -Frames "0-10" -Confirm
|
||||||
|
```
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
# Get-LastFrame.ps1
|
||||||
|
|
||||||
|
Extracts a specific frame of a video file to a PNG image.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
- **`-Video`** (required): Path to input video file
|
||||||
|
- **`-Image`** (required): Path to output PNG image file. Supports a printf-style numbering token like [Get-FramesRange.ps1](Get-FramesRange.md) — see [Filename Pattern](#filename-pattern). The output directory is created when it does not exist.
|
||||||
|
- **`-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)
|
||||||
|
|
||||||
|
## Filename Pattern
|
||||||
|
|
||||||
|
`-Image` may contain a printf-style numbering token, which is replaced with the **actual 0-based frame index that was extracted** — after `-Frame` has been resolved, so negative indices produce the real frame number:
|
||||||
|
|
||||||
|
| Token | Meaning | Frame 99 | Frame 7 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `%d` | No padding | `99` | `7` |
|
||||||
|
| `%04d` | Zero-padded to 4 digits | `0099` | `0007` |
|
||||||
|
| `%06d` | Zero-padded to 6 digits | `000099` | `000007` |
|
||||||
|
|
||||||
|
A path without a token is used **literally**, so existing commands such as `-Image last_frame.png` are unaffected (only one frame is produced, so no numbering is needed).
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-LastFrame.ps1 -Video video.mp4 -Image "frame_%04d.png"
|
||||||
|
# 100-frame video -> frame_0099.png
|
||||||
|
```
|
||||||
|
|
||||||
|
When the pattern resolves, the script reports the final name:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Total frames: 100 - extracting frame index 99
|
||||||
|
Output pattern resolved to: frame_0099.png
|
||||||
|
Frame saved to: frame_0099.png
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** the token is expanded by the script, not by ffmpeg. ffmpeg would treat `%04d` in an output path as an image-sequence counter starting at `1` (writing `frame_0001.png` regardless of which frame was selected); expanding it here guarantees the number is the real video frame index, matching [Get-FramesRange.ps1](Get-FramesRange.md).
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
**Extract last frame:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-LastFrame.ps1 -Video video.mp4 -Image last_frame.png
|
||||||
|
```
|
||||||
|
|
||||||
|
**Extract specific frame:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-LastFrame.ps1 -Video video.mp4 -Image frame_100.png -Frame 100
|
||||||
|
```
|
||||||
|
|
||||||
|
**Extract first frame:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-LastFrame.ps1 -Video video.mp4 -Image first_frame.png -Frame 0
|
||||||
|
```
|
||||||
|
|
||||||
|
**Extract second-to-last frame:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-LastFrame.ps1 -Video video.mp4 -Image second_last.png -Frame -2
|
||||||
|
```
|
||||||
|
|
||||||
|
**Extract 10th frame from the end:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-LastFrame.ps1 -Video video.mp4 -Image frame_minus_10.png -Frame -10
|
||||||
|
```
|
||||||
|
|
||||||
|
**Using absolute paths:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-LastFrame.ps1 -Video "C:\Videos\input.mp4" -Image "C:\Frames\output.png" -Frame 50
|
||||||
|
```
|
||||||
|
|
||||||
|
**Filename pattern with the frame number:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-LastFrame.ps1 -Video video.mp4 -Image "frame_%04d.png"
|
||||||
|
# If video has 100 frames, creates: frame_0099.png
|
||||||
|
```
|
||||||
|
|
||||||
|
**Full output path with a pattern (directory is created if missing):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-LastFrame.ps1 -Video video.mp4 -Image "C:\Output\frame_%04d.png" -Frame 50
|
||||||
|
# Creates: C:\Output\frame_0050.png
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pattern with a negative frame index:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-LastFrame.ps1 -Video video.mp4 -Image "p_%06d.png" -Frame -2
|
||||||
|
# If video has 100 frames, creates: p_000098.png
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pattern without padding:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-LastFrame.ps1 -Video video.mp4 -Image "f_%d.png" -Frame 7
|
||||||
|
# Creates: f_7.png
|
||||||
|
```
|
||||||
|
|
||||||
|
## Safe Execution
|
||||||
|
|
||||||
|
This script supports `-WhatIf` and `-Confirm` for safe execution:
|
||||||
|
|
||||||
|
- **`-WhatIf`**: Simulate the operation without actually extracting the frame. Shows the resolved output name and what would be extracted; no output directory is created.
|
||||||
|
- **`-Confirm`**: Prompt for confirmation before extracting the frame.
|
||||||
|
|
||||||
|
**Simulate without extracting:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-LastFrame.ps1 -Video video.mp4 -Image frame.png -WhatIf
|
||||||
|
```
|
||||||
|
|
||||||
|
**Prompt before extraction:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-LastFrame.ps1 -Video video.mp4 -Image frame.png -Frame 100 -Confirm
|
||||||
|
```
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
# Get-VideoAudio.ps1
|
||||||
|
|
||||||
|
Extracts audio in either WAV or MP3 format from a video file.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
- **`-Video`** (required): Path to input video file (absolute or relative)
|
||||||
|
- **`-Output`** (optional): Path to output audio file (defaults to video filename with `.wav` or `.mp3` extension)
|
||||||
|
- **`-MP3`**: Extract audio as MP3 at 320 kbps instead of WAV
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
**Extract audio as WAV (default):**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-VideoAudio.ps1 -Video video.mp4
|
||||||
|
# Creates video.wav in the same directory
|
||||||
|
```
|
||||||
|
|
||||||
|
**Extract audio as MP3:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-VideoAudio.ps1 -Video video.mp4 -MP3
|
||||||
|
# Creates video.mp3 in the same directory
|
||||||
|
```
|
||||||
|
|
||||||
|
**Specify custom output path:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-VideoAudio.ps1 -Video video.mp4 -Output audio\soundtrack.wav
|
||||||
|
```
|
||||||
|
|
||||||
|
**Extract MP3 with custom output:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-VideoAudio.ps1 -Video video.mp4 -Output music.mp3 -MP3
|
||||||
|
```
|
||||||
|
|
||||||
|
**Using absolute paths:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-VideoAudio.ps1 -Video "C:\Videos\movie.mp4" -Output "D:\Audio\soundtrack.mp3" -MP3
|
||||||
|
```
|
||||||
|
|
||||||
|
**Using relative paths:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-VideoAudio.ps1 -Video ..\videos\clip.mp4 -Output .\audio\clip.wav
|
||||||
|
```
|
||||||
|
|
||||||
|
## Behavior
|
||||||
|
|
||||||
|
- If the video file has no audio stream, the script displays a message and exits without creating an output file
|
||||||
|
- WAV format uses PCM 16-bit encoding (lossless)
|
||||||
|
- MP3 format uses 320 kbps bitrate (highest quality)
|
||||||
|
- If output file already exists, it will be overwritten
|
||||||
|
|
||||||
|
## Safe Execution
|
||||||
|
|
||||||
|
This script supports `-WhatIf` and `-Confirm` for safe execution:
|
||||||
|
|
||||||
|
- **`-WhatIf`**: Simulate the operation without actually creating files. Shows what would be done.
|
||||||
|
- **`-Confirm`**: Prompt for confirmation before extracting audio.
|
||||||
|
|
||||||
|
**Simulate without making changes:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-VideoAudio.ps1 -Video video.mp4 -MP3 -WhatIf
|
||||||
|
```
|
||||||
|
|
||||||
|
**Prompt before extracting:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-VideoAudio.ps1 -Video video.mp4 -Output soundtrack.wav -Confirm
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
Requires `ffmpeg` and `ffprobe` to be installed and available in the system PATH.
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
# Get-VideoInfo.ps1
|
||||||
|
|
||||||
|
Extracts and displays video metadata from the comment tag in a video file's format metadata. The displayed fields are driven by `Get-VideoInfo.ini` (display name → JSON field name). The script also probes the video stream with ffprobe and reports the actual encoded resolution and frame rate alongside the metadata when they differ. Fields mapped to `frames_count` in the INI are always read directly from ffprobe rather than from the JSON metadata.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
- **`-Video`** (required): Path to input video file. Supports Windows wildcards (`*` and `?`) to match multiple files (e.g., `*.mp4`, `video?.mov`). When wildcards are used, metadata is extracted and displayed for each matching file sequentially, with separators between files.
|
||||||
|
- **`-Full`**: Output the parsed comment metadata as JSON (printed before the formatted block)
|
||||||
|
|
||||||
|
## Real vs metadata values
|
||||||
|
|
||||||
|
The script reads two pieces of information from ffprobe's video stream and uses them to enrich the formatted output:
|
||||||
|
|
||||||
|
- **Resolution**: any field whose value matches the `WxH` pattern (e.g. the metadata `resolution`) is shown with the actual encoded resolution appended when it differs — for example `Resolution: 720x1280 (real: 704x1280)`. WanGP / LTX-2 commonly record the requested resolution while the file is encoded at a slightly different size after the model's internal padding/cropping.
|
||||||
|
- **Frame rate / duration**: the duration of the `video_length` field is always computed from ffprobe's real frame rate (`r_frame_rate` of the first video stream). The JSON metadata's `force_fps` is intentionally ignored because some WanGP/LTX presets store non-numeric mode markers there (e.g. `"control"`). If ffprobe doesn't return a usable frame rate, the script falls back to 24 fps.
|
||||||
|
- **Actual frame count**: the `frames_count` field is always read directly from ffprobe (`nb_read_frames` of the first video stream) rather than from the JSON comment metadata. This gives the true encoded frame count of the file. Duration is computed using ffprobe's real frame rate, falling back to 24 fps if unavailable.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
**Display formatted metadata:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-VideoInfo.ps1 -Video video.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
Output example (metadata and actual resolution agree):
|
||||||
|
|
||||||
|
```text
|
||||||
|
----------------------------------------
|
||||||
|
File Name: video.mp4
|
||||||
|
Text Prompt: Perform accurate continuous lip sync with the spoken audio. A young woman is talking. Her lips, mouth and jaw move precisely and continuously in sync with every word and syllable of the spoken audio. Her teeth and tongue are visible as she speaks. Her lips never stop or freeze while the audio is playing.
|
||||||
|
Model: WanGP v10.9875 by DeepBeepMeep - LTX-2 2.3 Dev 22B
|
||||||
|
Resolution: 1280x720
|
||||||
|
Number of frames: 113 frames (4,7s, 24 fps)
|
||||||
|
Video Length: 113 frames (4,7s, 24 fps)
|
||||||
|
Seed: 153601012
|
||||||
|
Guidance: 4
|
||||||
|
Audio Guidance Scale: 7
|
||||||
|
Modality Guidance: 3
|
||||||
|
Guidance Rescale: 0,7
|
||||||
|
Num Inference Steps: 40
|
||||||
|
Creation Date: 2026-03-20 04:55:13
|
||||||
|
Generation Time: 1209s (20m 9s)
|
||||||
|
----------------------------------------
|
||||||
|
```
|
||||||
|
|
||||||
|
Output example (metadata resolution differs from the actual encoded resolution):
|
||||||
|
|
||||||
|
```text
|
||||||
|
----------------------------------------
|
||||||
|
File Name: 2026-05-31-12h25m16s_seed777652600_Remove the text from the foreground..mp4
|
||||||
|
Text Prompt: Remove the text from the foreground.
|
||||||
|
Model: WanGP v11.88 by DeepBeepMeep - LTX-2 2.3 Distilled 1.1 22B
|
||||||
|
Resolution: 720x1280 (real: 704x1280)
|
||||||
|
Number of frames: 185 frames (6,2s, 30 fps)
|
||||||
|
Video Length: 185 frames (6,2s, 30 fps)
|
||||||
|
...
|
||||||
|
----------------------------------------
|
||||||
|
```
|
||||||
|
|
||||||
|
**Display full JSON metadata:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-VideoInfo.ps1 -Video video.mp4 -Full
|
||||||
|
```
|
||||||
|
|
||||||
|
**Using relative paths:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-VideoInfo.ps1 -Video ..\videos\sample.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
**Process multiple files with wildcards:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-VideoInfo.ps1 -Video "*.mp4"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Process files matching a pattern:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Get-VideoInfo.ps1 -Video "video?.mp4"
|
||||||
|
.\Get-VideoInfo.ps1 -Video "C:\Videos\Project_*_v1.mp4"
|
||||||
|
```
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
# RIFE 4.26 on Windows 11 — Command-Line Installation Guide
|
||||||
|
|
||||||
|
There are two pieces you need: the **binary** (the `rife-ncnn-vulkan` executable) and the **model** (rife-v4.26 weights). The upstream nihui release is stuck at models up to v4.6, so you'll use **TNTwise's fork**, which maintains up-to-date model support including v4.26.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1 — Download the Binary
|
||||||
|
|
||||||
|
The actively maintained fork for newer models is TNTwise's `rife-ncnn-vulkan` at [https://github.com/TNTwise/rife-ncnn-vulkan](https://github.com/TNTwise/rife-ncnn-vulkan).
|
||||||
|
|
||||||
|
1. Go to: [https://github.com/TNTwise/rife-ncnn-vulkan/releases](https://github.com/TNTwise/rife-ncnn-vulkan/releases)
|
||||||
|
2. Download the latest Windows zip, e.g. `rife-ncnn-vulkan-YYYYMMDD-windows.zip`
|
||||||
|
3. Extract it to a folder, e.g. `C:\tools\rife-ncnn-vulkan\`
|
||||||
|
|
||||||
|
The package includes all binaries and models required — it is portable, so no CUDA or PyTorch runtime environment is needed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2 — Add to PATH
|
||||||
|
|
||||||
|
To run `rife-ncnn-vulkan.exe` from any directory (instead of always `cd`-ing into the install folder), add the extraction folder to your system **PATH**:
|
||||||
|
|
||||||
|
```text
|
||||||
|
C:\tools\rife-ncnn-vulkan\
|
||||||
|
```
|
||||||
|
|
||||||
|
You must also define the environment variable **RIFE_HOME** and set it to the same folder:
|
||||||
|
|
||||||
|
```text
|
||||||
|
RIFE_HOME=C:\tools\rife-ncnn-vulkan\
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** `RIFE_HOME` must point to the same directory that is added to PATH. RIFE locates its model files via the `-m` command-line flag, not through environment variables, but `RIFE_HOME` is still required.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3 — Basic Usage (Interpolate Between Two Images)
|
||||||
|
|
||||||
|
To interpolate between two images and produce a middle frame:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
rife-ncnn-vulkan.exe -0 frame0.png -1 frame1.png -o middle.png -m %RIFE_HOME%\rife-v4.26
|
||||||
|
```
|
||||||
|
|
||||||
|
To process a whole directory of frames:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
rife-ncnn-vulkan.exe -i input_frames\ -o output_frames\ -m %RIFE_HOME%\rife-v4.26
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Full CLI Reference
|
||||||
|
|
||||||
|
Key flags:
|
||||||
|
|
||||||
|
| Flag | Meaning |
|
||||||
|
| ------ | --------- |
|
||||||
|
| `-0 <path>` | First input image (jpg/png/webp) |
|
||||||
|
| `-1 <path>` | Second input image |
|
||||||
|
| `-o <path>` | Output image or directory |
|
||||||
|
| `-m <path>` | Model path (point to your `rife-v4.26` folder) |
|
||||||
|
| `-g <id>` | GPU device: `0`, `1`, `2` for GPU, **`-1` for CPU** |
|
||||||
|
| `-j <n:n:n>` | Thread counts for load:proc:save |
|
||||||
|
| `-n <count>` | Number of frames to generate between the two inputs (default: 1) |
|
||||||
|
| `-v` | Verbose output |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Running Without a GPU (CPU Mode)
|
||||||
|
|
||||||
|
You can run on CPU by setting `-g -1`.
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
rife-ncnn-vulkan.exe -0 frame0.png -1 frame1.png -o middle.png -m %RIFE_HOME%\rife-v4.26 -g -1
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also run on CPU, discrete GPU, and integrated GPU simultaneously:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
rife-ncnn-vulkan.exe -i input_frames\ -o output_frames\ -g -1,-1,0,1 -j 2:4,4,2,1:4
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important caveat:** CPU mode works but is significantly slower — expect 10–30× slower processing compared to even an integrated GPU. For a single pair of 1080p frames it's tolerable; for video interpolation it will be very slow.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Test Checklist
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
# Verify the binary runs
|
||||||
|
rife-ncnn-vulkan.exe -h
|
||||||
|
|
||||||
|
# Single frame interpolation test (CPU, no GPU needed)
|
||||||
|
rife-ncnn-vulkan.exe -0 A.png -1 B.png -o middle.png -m %RIFE_HOME%\rife-v4.26 -g -1 -v
|
||||||
|
```
|
||||||
|
|
||||||
|
If you get a Vulkan error with GPU mode, make sure your GPU drivers are up to date. CPU mode (`-g -1`) requires no Vulkan device at all.
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
# Stitch-InBetween.ps1
|
||||||
|
|
||||||
|
Generates interpolated in-between frames between two images using RIFE and encodes them into a video.
|
||||||
|
|
||||||
|
## Synopsis
|
||||||
|
|
||||||
|
Uses `rife-ncnn-vulkan` to interpolate frames between a first and last image, then encodes the result into an H.264 video using ffmpeg.
|
||||||
|
|
||||||
|
The script:
|
||||||
|
|
||||||
|
1. Validates that the input image files exist
|
||||||
|
2. Creates a dedicated temporary directory for input images
|
||||||
|
3. Copies the first and last images to the dedicated temp directory
|
||||||
|
4. Runs `rife-ncnn-vulkan.exe` to generate interpolated frames
|
||||||
|
5. Filters the generated frames to keep only the desired in-between sequence
|
||||||
|
6. Encodes the remaining frames into a video at the specified FPS
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
- **`-First`** (required): Full or relative path to the first image file (e.g., `First.png` or `D:\Images\First.png`)
|
||||||
|
- **`-Last`** (required): Full or relative path to the last image file (e.g., `Last.png` or `D:\Images\Last.png`)
|
||||||
|
- **`-TempDir`** (optional): Full absolute path to a temporary directory for intermediate PNG frames. Must end with `\`. Defaults to a system-generated temp subdirectory. This is separate from the dedicated input temp directory.
|
||||||
|
- **`-Frames`** (required): Number of frames to generate in the output video. Must be greater than 0
|
||||||
|
- **`-Video`** (required): Output video filename. If relative, resolved against current directory
|
||||||
|
- **`-FPS`** (optional): Frames per second for the output video (default: `24`)
|
||||||
|
|
||||||
|
## Frame Generation
|
||||||
|
|
||||||
|
The number of frames passed to `rife-ncnn-vulkan` is computed as:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Count = 4 + (($Frames - 1) * 2)
|
||||||
|
EndsAt = Count / 2
|
||||||
|
```
|
||||||
|
|
||||||
|
After generation, `rife-ncnn-vulkan` outputs files named `%08d.png`. The script deletes:
|
||||||
|
|
||||||
|
- `00000001.png` (copy of the first input image)
|
||||||
|
- All files with index greater than `EndsAt` (copy of the last input image and beyond)
|
||||||
|
|
||||||
|
Only the true interpolated frames (`00000002.png` through `00000{EndsAt}.png`) are passed to ffmpeg.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
**Basic interpolation:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Stitch-InBetween.ps1 -First "frame1.png" -Last "frame2.png" -Frames 30 -Video "output.mp4"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Full paths with custom FPS:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Stitch-InBetween.ps1 -First "D:\Images\start.png" -Last "D:\Images\end.png" -Frames 60 -Video "result.mp4" -FPS 30
|
||||||
|
```
|
||||||
|
|
||||||
|
**Absolute paths for input and output:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Stitch-InBetween.ps1 -First "C:\Work\Frames\A.png" -Last "C:\Work\Frames\B.png" -Frames 24 -Video "D:\Output\clip.mp4"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Custom temp directory:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Stitch-InBetween.ps1 -First "A.png" -Last "B.png" -Frames 24 -Video "clip.mp4" -TempDir "D:\Temp\rife\"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Safe Execution
|
||||||
|
|
||||||
|
This script supports `-WhatIf` and `-Confirm` for safe execution:
|
||||||
|
|
||||||
|
- **`-WhatIf`**: Simulate the operation without actually generating frames or creating the video. Shows what would be done.
|
||||||
|
- **`-Confirm`**: Prompt for confirmation before clearing temp directory, generating frames, and creating the video.
|
||||||
|
|
||||||
|
**Simulate without making changes:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Stitch-InBetween.ps1 -First "frame1.png" -Last "frame2.png" -Frames 30 -Video "output.mp4" -WhatIf
|
||||||
|
```
|
||||||
|
|
||||||
|
**Prompt before operations:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Stitch-InBetween.ps1 -First "frame1.png" -Last "frame2.png" -Frames 30 -Video "output.mp4" -Confirm
|
||||||
|
```
|
||||||
|
|
||||||
|
## Existing Files and Temporary Directory
|
||||||
|
|
||||||
|
- **`-Video`**: if a file already exists at this path, it is deleted before generation starts. The old file is gone even if the generation fails later. Use `-WhatIf` to see the deletion first.
|
||||||
|
- **`-TempDir`**: if the folder already exists, **everything inside it is deleted recursively** before generation starts. Pass a dedicated folder that holds nothing you need. The default is a new folder under the system temporary directory, so no existing file is deleted.
|
||||||
|
- The input frames are copied to a second temporary folder under the system temporary directory. This folder is deleted after rife-ncnn-vulkan finishes successfully.
|
||||||
|
- Intermediate frames in `-TempDir` are deleted as described in [Frame Generation](#frame-generation).
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Requires `rife-ncnn-vulkan.exe` (TNTwise fork, RIFE 4.26) in the system PATH
|
||||||
|
- Requires `ffmpeg` in the system PATH
|
||||||
|
- Requires the `RIFE_HOME` environment variable pointing to the rife-ncnn-vulkan install directory (the same folder that is on PATH). The script resolves the model directory from `%RIFE_HOME%\rife-v4.26`.
|
||||||
|
- Requires the `NCNN_GPU_ID` environment variable set to the desired GPU device ID passed to rife-ncnn-vulkan via the `-g` flag:
|
||||||
|
- `-1` — CPU only (no Vulkan GPU required).
|
||||||
|
- `0` — First GPU.
|
||||||
|
- `1`, `2`, etc. — Additional GPUs.
|
||||||
|
- The script creates a dedicated temporary directory for input images, separate from the `-TempDir` parameter
|
||||||
|
- Input images can be located anywhere on the filesystem and are copied to the dedicated temp directory
|
||||||
|
- The dedicated input temp directory is automatically cleaned up after frame generation
|
||||||
|
- The output temp directory (`-TempDir`) is created if it does not exist; its contents are always cleared before each run
|
||||||
|
- Output codec: libx264, CRF 8, pixel format yuv420p
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# Upscalers - upscayl-bin Windows 11 — Command-Line Installation Guide
|
||||||
|
|
||||||
|
There are two pieces you need: the **binary** (the `upscayl-bin` executable) and the **model** (upscayl models).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1 — Download the Binary
|
||||||
|
|
||||||
|
The upscayl-bin binary can be downloaded from the official upscayl repository or from the releases page.
|
||||||
|
|
||||||
|
1. Go to: [https://github.com/TNTwise/Universal-NCNN-Upscaler/releases](https://github.com/TNTwise/Universal-NCNN-Upscaler/releases)
|
||||||
|
2. Download the latest Windows zip, e.g. `upscayl-bin-YYYYMMDD-windows.zip`
|
||||||
|
3. Extract it to a folder, e.g. `C:\tools\upscayl-bin\`
|
||||||
|
|
||||||
|
The package includes all binaries required — it is portable, so no CUDA or PyTorch runtime environment is needed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2 — Add to PATH
|
||||||
|
|
||||||
|
To run `upscayl-bin.exe` from any directory (instead of always `cd`-ing into the install folder), add the extraction folder to your system **PATH**:
|
||||||
|
|
||||||
|
```text
|
||||||
|
C:\tools\upscayl-bin\
|
||||||
|
```
|
||||||
|
|
||||||
|
Then open a new terminal and verify:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
upscayl-bin -h
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 3 — Download Models
|
||||||
|
|
||||||
|
The models can be downloaded from the links found in the `README.md` file of the upscayl-bin directory.
|
||||||
|
|
||||||
|
The default model used by the scripts is the `4xNomos8kSC` model.
|
||||||
|
|
||||||
|
1. Go to: [https://github.com/TNTwise/Upscaling-Models-NCNN/releases/tag/Realistic](https://github.com/TNTwise/Upscaling-Models-NCNN/releases/tag/Realistic)
|
||||||
|
2. Download the `4xNomo8kSC.zip` file
|
||||||
|
3. Extract the 2 files (`4xNomos8kSC.bin` and `4xNomos8kSC.param`) directly to the root of the **models** directory (create it if it doesn't exist) in `upscayl-bin`, e.g. `C:\tools\upscayl-bin\models\`
|
||||||
|
|
||||||
|
You can also download other models if you want to use them instead of the default `4xNomos8kSC` model. They should be extracted to the same `models` directory.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 4 - Set UPSCAYL_HOME
|
||||||
|
|
||||||
|
The scripts that use upscayl-bin (for example `Clone-Image.ps1 -Upscale`) read the environment variable **UPSCAYL_HOME**. Set it to the same folder that is on PATH. The models are read from `%UPSCAYL_HOME%\models`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
UPSCAYL_HOME=C:\tools\upscayl-bin\
|
||||||
|
```
|
||||||
|
|
||||||
|
Set the variable as a persistent user or system environment variable, then open a new terminal. The scripts also need `NCNN_GPU_ID`; see [NCNN_GPU_ID](index.md#ncnn_gpu_id).
|
||||||
Reference in new issue
Block a user