Compare commits

...

24 Commits

Author SHA1 Message Date
f7c4acfebb Remove FastAbsoluteSaver node (moved to ComfyUI-JSON-Dynamic)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 16:11:38 +01:00
6c7f618bb0 Only add sharpness score metadata when scores are actually connected
Return None for scores_list when scores_info input is not connected,
and skip writing score metadata in that case for both images and videos.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 11:26:36 +01:00
f02851d88a Fix argument too long error by using ffmetadata file for video metadata
Write metadata to a temp file and pass via -i/-map_metadata instead of
command-line -metadata flags to avoid OS argument length limits with
large ComfyUI workflow JSON.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 00:11:20 +01:00
59bd805920 Fix ValueError flush of closed file in video encoding
Replace communicate() with direct stderr.read() + wait() to avoid
double-closing stdin. Catch BrokenPipeError for early ffmpeg exits.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 23:12:44 +01:00
3297f3a203 Add metadata embedding to video output
Writes sharpness scores (avg + per-frame) and optionally the ComfyUI
workflow/prompt as ffmpeg container metadata in mp4/webm files.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 23:01:56 +01:00
3b87ac820f Add video saving support (mp4/webm) to FastAbsoluteSaver
Adds mp4 (H.264) and webm (VP9) output formats with configurable FPS,
CRF, and pixel format. Includes auto-discovery of ffmpeg from bundled
binary, imageio_ffmpeg, system PATH, or automatic static download.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 23:00:17 +01:00
b23773c7c2 Update fast_saver.py 2026-01-21 11:21:00 +01:00
8560f24d36 Update fast_saver.py 2026-01-21 10:45:09 +01:00
c40c1fd82c Update fast_saver.py 2026-01-20 01:00:03 +01:00
07acefffc1 Update README.md 2026-01-20 00:46:59 +01:00
b30e2d0233 Update README.md 2026-01-20 00:40:53 +01:00
dd6a9aefd7 Upload files to "assets" 2026-01-20 00:39:52 +01:00
60362e3514 Upload files to "example_workflows" 2026-01-20 00:39:26 +01:00
f03979a767 Delete example_workflows/comfyui-sharp-example.json 2026-01-20 00:38:06 +01:00
c32a4bcb32 Update README.md 2026-01-20 00:34:47 +01:00
162699a4a2 Merge pull request 'webp' (#4) from webp into main
Reviewed-on: #4
2026-01-20 00:31:09 +01:00
f63b837a2c Update fast_saver.py 2026-01-20 00:21:42 +01:00
b54b4329ca Update fast_saver.py 2026-01-20 00:20:21 +01:00
1bde14bd97 Update fast_saver.py 2026-01-20 00:08:41 +01:00
a2d79a7e6c Merge pull request 'fast-saver' (#3) from fast-saver into main
Reviewed-on: #3
2026-01-19 23:57:45 +01:00
24a59a6da2 Update fast_saver.py 2026-01-19 23:44:40 +01:00
099ce948ae Update __init__.py 2026-01-19 23:09:21 +01:00
44f3130a15 Update __init__.py 2026-01-19 23:05:03 +01:00
178247c79f Add fast_saver.py 2026-01-19 23:04:07 +01:00
5 changed files with 288 additions and 148 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
ffmpeg_bin/

134
README.md
View File

@@ -1,24 +1,30 @@
# 🔪 ComfyUI Sharp Frame Selector
# ComfyUI Sharpness Helper Nodes
A collection of high-performance custom nodes for **ComfyUI** designed to detect blur, calculate sharpness scores, and automatically extract the best frames from videos or image batches.
A high-performance custom node suite for **ComfyUI** designed to detect blur, calculate sharpness scores (Laplacian Variance), and efficiently extract or filter the best frames from videos and image batches.
This pack includes two distinct approaches:
1. **Parallel Video Loader:** A multi-threaded, path-based loader for processing massive video files directly from disk (Low RAM usage).
2. **Standard Sharpness Duo:** A classic filter setup for processing images/latents *inside* your existing workflow.
This pack is for a personnal project:
1. **Dataset Creation:** Extracting only the sharpest frames from massive movie files without crashing RAM.
2. **Generation Filtering:** Automatically discarding blurry frames from Wan or img2img outputs.
---
![Nodes Diagram](assets/nodes.png)
![node](assets/nodes.png)
---
## 🚀 Key Features
### 1. New: Parallel Video Loader (Path-Based)
### 1. Parallel Video Loader (Path-Based)
* **Zero-RAM Scanning:** Scans video files directly from disk without decoding every frame to memory.
* **Multi-Threaded:** Uses all CPU cores to calculate sharpness scores at high speed.
* **Batching Support:** Includes a "Page" system to process long movies in chunks (e.g., minute-by-minute) without restarting ComfyUI.
* **Smart Selection:** Automatically skips "adjacent" frames to ensure you get a diverse selection of sharp images.
* **Multi-Threaded:** Uses all CPU cores to calculate sharpness scores at high speed (1000s of frames per minute).
* **Smart Batching:** Includes an auto-incrementing "Page" system to process long movies in chunks (e.g., minute-by-minute) without restarting ComfyUI.
* **Lazy Loading:** Only decodes and loads the final "Best N" frames into ComfyUI tensors.
### 2. Standard Sharpness Duo (Tensor-Based)
### 2. Fast Absolute Saver (Metadata)
* **Multi-Threaded Saving:** Spawns parallel workers to saturate SSD write speeds (bypassing standard PIL bottlenecks).
* **No UI Lag:** Saves images in the background without trying to render Base64 previews in the browser, preventing interface freezes.
* **Metadata Embedding:** Automatically embeds the sharpness score into the PNG/WebP metadata for dataset curation.
* **Smart Naming:** Uses original video frame numbers in filenames (e.g., `frame_001450.png`) instead of arbitrary counters.
### 3. Standard Sharpness Duo (Tensor-Based)
* **Workflow Integration:** Works with any node that outputs an `IMAGE` batch (e.g., AnimateDiff, VideoHelperSuite).
* **Precision Filtering:** Sorts and filters generated frames before saving or passing to a second pass (img2img).
@@ -29,4 +35,108 @@ This pack includes two distinct approaches:
1. Clone this repository into your `custom_nodes` folder:
```bash
cd ComfyUI/custom_nodes/
git clone https://github.com/ethanfel/ComfyUI-Sharp-Selector.git
git clone [https://github.com/YOUR_USERNAME/ComfyUI-Sharpness-Helper.git](https://github.com/YOUR_USERNAME/ComfyUI-Sharpness-Helper.git)
```
2. Install dependencies (if needed):
```bash
pip install opencv-python numpy
```
3. Restart ComfyUI.
---
## 🛠️ Node Documentation
### 1. Parallel Video Loader (Sharpness)
**Category:** `BetaHelper/Video`
This is the recommended node for **Dataset Creation** or finding good frames in **Long Movies**. It inputs a file path, scans it in parallel, and only loads the final "Best N" frames into memory.
| Input | Description |
| :--- | :--- |
| **video_path** | Absolute path to your video file (e.g., `D:\Movies\input.mp4`). |
| **batch_index** | **Critical.** Connect a **Primitive Node** here set to `increment`. This controls which "chunk" of the video you are viewing. |
| **scan_limit** | How many frames to process per batch (e.g., `1440`). |
| **frame_scan_step** | Speed up scanning by checking every Nth frame (e.g., `5` checks frames 0, 5, 10...). |
| **manual_skip_start** | Global offset (e.g., set to `2000` to always ignore the opening credits). |
**Outputs:**
* `images`: The batch of the sharpest frames found.
* `scores_info`: String containing frame indices and scores (Connect to Saver).
* `batch_int`: The current batch number.
* `batch_status`: Human-readable status (e.g., *"Batch 2: Skipped 2880 frames..."*).
> **💡 Pro Tip:** To scan a movie continuously, connect a **Primitive Node** to `batch_index`, set it to **increment**, and enable "Auto Queue" in ComfyUI.
---
### 2. Fast Absolute Saver (Metadata)
**Category:** `BetaHelper/IO`
A "Pro-Grade" saver designed for speed. It bypasses relative paths and UI previews.
| Input | Description |
| :--- | :--- |
| **output_path** | Absolute path to save folder (e.g., `D:\Datasets\Sharp_Output`). |
| **filename_prefix** | Base name for files (e.g., `matrix_movie`). |
| **max_threads** | **0 = Auto** (Uses all CPU cores). Set manually to limit CPU usage. |
| **save_format** | `png` (Fastest) or `webp` (Smaller size). |
| **filename_with_score** | If True, appends score to filename: `frame_001450_1500.png`. |
| **scores_info** | Connect this to the `scores_info` output of the Parallel Loader to enable smart naming. |
**Performance Note:**
* **PNG:** Uses `compress_level=1` for maximum speed.
* **WebP:** Avoid `webp_method=6` unless you need max compression; it is very CPU intensive. `4` is the recommended balance.
---
### 3. Sharpness Analyzer & Selector (The Duo)
**Category:** `BetaHelper/Image`
Use these when you already have images inside your workflow (e.g., from a generation or a standard Load Video node).
#### Node A: Sharpness Analyzer
* **Input:** `IMAGE` batch.
* **Action:** Calculates the Laplacian Variance for every image in the batch.
* **Output:** Passes the images through + a generic score list.
#### Node B: SharpFrame Selector
* **Input:** `IMAGE` batch (from Analyzer).
* **Action:** Sorts the batch based on the scores and picks the top N frames.
* **Output:** A reduced batch containing only the sharpest images.
---
## ⚖️ Which Node Should I Use?
| Feature | **Parallel Video Loader** | **Standard Duo** |
| :--- | :--- | :--- |
| **Input Type** | File Path (`String`) | Image Tensor (`IMAGE`) |
| **Best For** | **Long Videos / Movies** | **Generations / Short Clips** |
| **Memory Usage** | Very Low (Only loads final frames) | High (Loads all frames to RAM first) |
| **Speed** | ⚡ **Ultra Fast** (Multi-core) | 🐢 Standard (Single-core) |
| **Workflow Stage** | Start of Workflow | Middle/End of Workflow |
---
## 📝 Example Workflows
### Batch Processing a Movie for Training Data
1. Add **Parallel Video Loader**.
2. Connect a **Primitive Node** to `batch_index` (Control: `increment`).
3. Set `scan_limit` to `1000` and `frame_scan_step` to `5`.
4. Connect `images` and `scores_info` to **Fast Absolute Saver**.
5. Enable **Auto Queue** in ComfyUI extra options.
* *Result: ComfyUI will loop through your movie, extracting the 4 sharpest frames from every ~1000 frame chunk automatically.*
### Filtering AnimateDiff Output
1. AnimateDiff Generation -> **Sharpness Analyzer**.
2. Analyzer Output -> **SharpFrame Selector** (Select Best 1).
3. Selector Output -> **Face Detailer** or **Upscaler**.
* *Result: Only the clearest frame from your animation is sent to the upscaler, saving time on blurry frames.*
---
## Credits
* Built using `opencv-python` for Laplacian Variance calculation.
* Parallel processing logic for efficient large-file handling.

View File

@@ -4,13 +4,13 @@ from .parallel_loader import ParallelSharpnessLoader
NODE_CLASS_MAPPINGS = {
"SharpnessAnalyzer": SharpnessAnalyzer,
"SharpFrameSelector": SharpFrameSelector,
"ParallelSharpnessLoader": ParallelSharpnessLoader
"ParallelSharpnessLoader": ParallelSharpnessLoader,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"SharpnessAnalyzer": "1. Sharpness Analyzer",
"SharpFrameSelector": "2. Sharp Frame Selector",
"ParallelSharpnessLoader": "3. Parallel Video Loader (Sharpness)"
"ParallelSharpnessLoader": "3. Parallel Video Loader (Sharpness)",
}
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

After

Width:  |  Height:  |  Size: 347 KiB

View File

@@ -1,8 +1,8 @@
{
"id": "4fbf6f31-0f7b-4465-8ec8-25df4862e076",
"revision": 0,
"last_node_id": 34,
"last_link_id": 42,
"last_node_id": 35,
"last_link_id": 44,
"nodes": [
{
"id": 31,
@@ -33,69 +33,7 @@
"version": "7.5.2",
"input_ue_unconnectable": {}
}
},
"widgets_values": []
},
{
"id": 29,
"type": "ParallelSharpnessLoader",
"pos": [
4992,
-1024
],
"size": [
320,
262
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "images",
"type": "IMAGE",
"links": [
42
]
},
{
"name": "scores_info",
"type": "STRING",
"links": []
},
{
"name": "batch_int",
"type": "INT",
"links": null
},
{
"name": "batch_status",
"type": "STRING",
"links": [
39
]
}
],
"properties": {
"aux_id": "ComfyUI-Sharp-Selector.git",
"ver": "dab38a1fbf0077655fe568d500866fce6ecc857d",
"Node name for S&R": "ParallelSharpnessLoader",
"ue_properties": {
"widget_ue_connectable": {},
"input_ue_unconnectable": {},
"version": "7.5.2"
}
},
"widgets_values": [
"C:\\path\\to\\video.mp4",
0,
1440,
1,
30,
24,
2000
]
},
{
"id": 1,
@@ -109,7 +47,7 @@
174
],
"flags": {},
"order": 6,
"order": 4,
"mode": 0,
"inputs": [
{
@@ -155,64 +93,6 @@
0
]
},
{
"id": 34,
"type": "Note",
"pos": [
4224,
-1120
],
"size": [
416,
736
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [],
"outputs": [],
"properties": {},
"widgets_values": [
"📌 ComfyUI Sharpness Tools Explained\n\n1. Parallel Video Loader (The \"Loader\")\n\n Best for: Processing long videos or movie files directly from disk.\n\n How it works: It opens the video file itself and uses multi-threading (parallel CPU cores) to scan thousands of frames without loading them into memory. It only \"decodes\" the final few sharpest frames.\n\n Use Case: Extracting dataset images, finding high-quality frames from a raw movie file, or scanning 10,000 frames without crashing your RAM.\n\n Key Feature: Features a \"Batch Counter\" to automatically page through long videos (e.g., scan minute 0-1, then minute 1-2).\n\n2. Standard Sharpness Duo (The \"Filter\")\n\n Best for: Processing images already inside your workflow (e.g., after an img2img pass, or a short generated GIF).\n\n How it works:\n\n Node A (Analyzer): Assigns a score to every image in the batch.\n\n Node B (Selector): Picks the best ones based on those scores.\n\n Use Case: Filtering bad generations, picking the best frame from a small batch of AnimateDiff results, or cleaning up a sequence.\n\n Limitation: It is single-threaded and requires all images to be loaded in VRAM/RAM first (slow for long videos).\n\n🚀 Which one to use?\n\n Starting from a Video File? → Use Parallel Loader.\n\n Starting from a Generation/Latent? → Use Standard Duo."
],
"color": "#432",
"bgcolor": "#653"
},
{
"id": 33,
"type": "SaveImage",
"pos": [
5824,
-1024
],
"size": [
270,
58
],
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 42
}
],
"outputs": [],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.9.2",
"ue_properties": {
"widget_ue_connectable": {},
"input_ue_unconnectable": {}
},
"Node name for S&R": "SaveImage"
},
"widgets_values": [
"sharp/img_"
]
},
{
"id": 32,
"type": "easy showAnything",
@@ -225,7 +105,7 @@
96
],
"flags": {},
"order": 5,
"order": 6,
"mode": 0,
"inputs": [
{
@@ -268,7 +148,7 @@
26
],
"flags": {},
"order": 3,
"order": 1,
"mode": 0,
"inputs": [
{
@@ -298,6 +178,147 @@
}
},
"widgets_values": []
},
{
"id": 35,
"type": "FastAbsoluteSaver",
"pos": [
5856,
-1024
],
"size": [
306.3776153564453,
270
],
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 43
},
{
"name": "scores_info",
"shape": 7,
"type": "STRING",
"link": 44
}
],
"outputs": [],
"properties": {
"aux_id": "ComfyUI-Sharp-Selector.git",
"ver": "162699a4a23219ac5ac75f398a17e67c3767da46",
"ue_properties": {
"widget_ue_connectable": {},
"input_ue_unconnectable": {}
},
"Node name for S&R": "FastAbsoluteSaver"
},
"widgets_values": [
"D:\\Datasets\\Sharp_Output",
"frame",
"png",
0,
false,
"sharpness_score",
true,
100,
4
]
},
{
"id": 29,
"type": "ParallelSharpnessLoader",
"pos": [
4992,
-1024
],
"size": [
320,
262
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "images",
"type": "IMAGE",
"links": [
43
]
},
{
"name": "scores_info",
"type": "STRING",
"links": [
44
]
},
{
"name": "batch_int",
"type": "INT",
"links": null
},
{
"name": "batch_status",
"type": "STRING",
"links": [
39
]
}
],
"properties": {
"aux_id": "ComfyUI-Sharp-Selector.git",
"ver": "dab38a1fbf0077655fe568d500866fce6ecc857d",
"Node name for S&R": "ParallelSharpnessLoader",
"ue_properties": {
"widget_ue_connectable": {},
"input_ue_unconnectable": {},
"version": "7.5.2"
}
},
"widgets_values": [
"C:\\path\\to\\video.mp4",
0,
1440,
1,
30,
24,
2000
]
},
{
"id": 34,
"type": "Note",
"pos": [
4224,
-1120
],
"size": [
416,
736
],
"flags": {},
"order": 3,
"mode": 0,
"inputs": [],
"outputs": [],
"properties": {
"ue_properties": {
"widget_ue_connectable": {},
"version": "7.5.2",
"input_ue_unconnectable": {}
}
},
"widgets_values": [
"📝 Smart Dataset Extraction Workflow\n\n1. Parallel Video Loader (The Source)\n\n What it does: Scans your video file directly from the hard drive using multi-threading. It does not load the whole video into RAM.\n\n Batching: Uses the batch_index (Primitive Node) to \"page\" through the movie.\n\n Example: If scan_limit is 1440, Batch 0 scans frames 0-1440, Batch 1 scans 1440-2880, etc.\n\n Selection: It calculates sharpness (Laplacian Variance) and only decodes the \"Best N\" frames to send downstream.\n\n2. Fast Absolute Saver (The Destination)\n\n What it does: Saves images instantly to your SSD using parallel workers, bypassing the slow ComfyUI preview window.\n\n Smart Naming: Connect scores_info from the Loader to the Saver! This allows files to be named using the original video frame number (e.g., movie_frame_00450.png) rather than a random batch counter.\n\n Metadata: Embeds the sharpness score into the PNG/WebP metadata for future filtering.\n\n⚠ Usage Tips:\n\n Automation: Set batch_index to \"Increment\" (on the Primitive Node) and enable \"Auto Queue\" in ComfyUI options to process the entire movie automatically.\n\n Monitoring: Watch the Console Window (black command prompt) for progress logs. The saver does not preview images in the UI to prevent lag.\n\n Safety: The saver uses absolute paths and overwrites files with the same name. Use a unique filename_prefix for each new video source."
],
"color": "#432",
"bgcolor": "#653"
}
],
"links": [
@@ -318,12 +339,20 @@
"STRING"
],
[
42,
43,
29,
0,
33,
35,
0,
"IMAGE"
],
[
44,
29,
1,
35,
1,
"STRING"
]
],
"groups": [],
@@ -333,10 +362,10 @@
"ue_links": [],
"links_added_by_ue": [],
"ds": {
"scale": 0.8264462809917354,
"scale": 1.1,
"offset": [
-2054.6525613149634,
1737.5186871750661
-3048.698587382934,
1363.985488079904
]
},
"frontendVersion": "1.36.14",