fix: instance_id accepts STRING input (v2.1.2)

Legacy workflows pass instance_id="default" (string) from old v1 nodes.
ComfyUI runs validate_inputs BEFORE execute(), so the in-node _coerce_int
fallback never fires — the workflow errors before reaching our handler.

Change schema input type from INT to STRING in both FaceDetectionNode (v3)
and FaceDetectionNodeV1 (fallback). instance_id is only used as a dict key
(f"{instance_id}"), never arithmetic, so the narrowing is safe.

- v3 schema: IntegerInput → StringInput
- v1 schema: ("INT", ...) → ("STRING", ...)
- Remove instance_id from _INT_DEFAULTS and VALIDATE_INPUTS INT loop
- Simplify coercion to str(instance_id) if not None else "0"
- Bump version 2.1.1 → 2.1.2
This commit is contained in:
limbicnation
2026-04-20 19:08:36 +02:00
parent 68c40eade0
commit 5cffd4c3dd
3 changed files with 33 additions and 18 deletions
+5 -2
View File
@@ -51,7 +51,7 @@ A ComfyUI custom node for face detection and cropping using OpenCV Haar cascades
| output_mode | Combo | — | largest_face | `largest_face` or `all_faces` |
| temporal_smoothing | Int | 0–100 | 0 | 0=disabled (image mode) · 1–100=EMA smoothing strength for video |
| output_height | Int | 256–2048 | 512 | Output height for cropped faces (width derived from aspect ratio) |
| instance_id | Int | 0–9999 | 0 | Unique ID for temporal smoothing state across video frames |
| instance_id | String | — | "0" | Unique ID for temporal smoothing (share across frames). Use "0" for image mode |
| classifier_type | Combo | — | default | Haar cascade: `default` or `alternative` |
| face_output_format | Combo | — | strip | `strip` (horizontal) or `individual` (separate batch items) |
| padding | Int | 0–256 | 0 | Legacy padding in pixels — if >0, overrides `auto_padding_ratio` |
@@ -69,10 +69,13 @@ When processing video frames through ComfyUI, face detection bboxes can jitter f
- Set `temporal_smoothing` to 1–100 (higher = more smoothing)
- Use a consistent `instance_id` across all frames in the same video sequence
- Set to `0` for single-image mode (no smoothing)
- Set to `"0"` for single-image mode (no smoothing)
## Changelog
### v2.1.2
- **FIX**: `instance_id` input type changed from INT to STRING. Legacy workflows pass `instance_id="default"` (string) from old v1 nodes — ComfyUI's `validate_inputs` runs before `execute()`, so `_coerce_int` never fires. Since `instance_id` is only a dict key, STRING is the correct type.
### v2.1.1
- **FIX**: `temporal_smoothing` input validation error — moved to optional section in `INPUT_TYPES` to prevent ComfyUI framework-level `int()` coercion crash when legacy workflows pass string `"default"` (from `classifier_type`) into this slot via positional `widgets_values` mapping
- **FIX**: Added `_coerce_int()` helper for safe string→int conversion with fallback to defaults, applied defensively in both v1 and v3 execute methods
+27 -15
View File
@@ -1,7 +1,18 @@
"""
FaceDetectionNode — Optimized for H100 Cloud & LTX-Video Avatar Pipelines
========================================================================
Version: 2.1.1
Version: 2.1.2
CHANGELOG from v2.1.1 → v2.1.2:
1. FIX: instance_id input type changed from INT to STRING. Legacy workflows
pass instance_id="default" (string) from old v1 nodes. ComfyUI runs
validate_inputs BEFORE execute(), so the in-node _coerce_int fallback
never fires — the workflow errors before reaching our handler. Since
instance_id is only used as a dict key (f"{instance_id}"), never
arithmetic, STRING is the correct type. Accepts any string value now
("0", "default", "session-1", etc.).
2. Removed instance_id from _INT_DEFAULTS and VALIDATE_INPUTS INT loop.
3. Simplified coercion to str(instance_id) if not None else "0".
CHANGELOG from v2.1.0 → v2.1.1:
1. FIX: temporal_smoothing input validation error — moved to optional section in
@@ -60,7 +71,7 @@ try:
from comfy_api.v0_0_3_io import (
ComfyNode, Schema, InputBehavior, NumberDisplay,
IntegerInput, FloatInput, ImageInput, ImageOutput,
ComboInput, NodeOutput,
ComboInput, NodeOutput, StringInput,
)
COMFY_V3: bool = True
except ImportError:
@@ -92,9 +103,9 @@ _INT_DEFAULTS: dict[str, int] = {
"auto_padding_ratio": 35,
"min_face_size": 64,
"output_height": 512,
"instance_id": 0,
"padding": 0,
}
# instance_id was removed from _INT_DEFAULTS — it's now STRING type
def _coerce_int(value, name: str, default: int | None = None) -> int:
@@ -378,10 +389,10 @@ if COMFY_V3:
display_name="Output Height (px)",
min=256, max=2048, default=512,
tooltip="Output height for cropped faces (width derived from aspect ratio)"),
IntegerInput("instance_id",
StringInput("instance_id",
display_name="Instance ID",
min=0, max=9999, default=0,
tooltip="Unique ID for temporal smoothing state (share across frames in same video sequence)"),
default="0",
tooltip="Unique ID for temporal smoothing (share across frames in same video sequence). Use '0' for image mode."),
ComboInput("classifier_type",
options=["default", "alternative"],
behavior=InputBehavior.optional),
@@ -404,7 +415,7 @@ if COMFY_V3:
)
@classmethod
def _get_temporal(cls, instance_id: int, smoothing: int) -> TemporalState:
def _get_temporal(cls, instance_id: str, smoothing: int) -> TemporalState:
key = f"{instance_id}"
if key not in cls._temporal_state:
alpha = smoothing / 100.0 if smoothing > 0 else 1.0
@@ -447,14 +458,14 @@ if COMFY_V3:
face_output_format: str = "strip",
temporal_smoothing=70,
output_height=512,
instance_id=0,
instance_id="0",
classifier_type: str = "default",
padding=0,
) -> NodeOutput:
# Defensive: coerce INT inputs that may arrive as strings
temporal_smoothing = _coerce_int(temporal_smoothing, "temporal_smoothing", 70)
output_height = _coerce_int(output_height, "output_height", 512)
instance_id = _coerce_int(instance_id, "instance_id", 0)
instance_id = str(instance_id) if instance_id is not None else "0"
padding = _coerce_int(padding, "padding", 0)
target_ratio = ASPECT_RATIOS.get(aspect_ratio, None)
@@ -687,9 +698,9 @@ class FaceDetectionNodeV1:
"output_height": ("INT", {
"default": 512, "min": 256, "max": 2048,
}),
"instance_id": ("INT", {
"default": 0, "min": 0, "max": 9999,
"tooltip": "Unique ID for temporal smoothing across video frames",
"instance_id": ("STRING", {
"default": "0",
"tooltip": "Unique ID for temporal smoothing (share across frames in same video sequence). Use '0' for image mode.",
}),
# Backward compat: old v1.x workflows pass this
"face_output_format": (["strip", "individual"], {
@@ -723,10 +734,10 @@ class FaceDetectionNodeV1:
framework validation doesn't crash.
"""
# Coerce INT inputs that may arrive as strings from positional widget mapping
# Note: instance_id is now STRING — no longer needs INT coercion
for name, val in [
("temporal_smoothing", temporal_smoothing),
("output_height", output_height),
("instance_id", instance_id),
("padding", padding),
]:
if val is not None and not isinstance(val, int):
@@ -743,7 +754,7 @@ class FaceDetectionNodeV1:
return True
@classmethod
def _get_temporal(cls, instance_id: int, smoothing: int) -> TemporalState:
def _get_temporal(cls, instance_id: str, smoothing: int) -> TemporalState:
if instance_id not in cls._temporal_cache:
alpha = smoothing / 100.0 if smoothing > 0 else 1.0
cls._temporal_cache[instance_id] = TemporalState(alpha=alpha)
@@ -768,9 +779,10 @@ class FaceDetectionNodeV1:
):
# Defensive: coerce optional INT inputs that may arrive as strings
# from legacy workflows with misaligned widgets_values
# Note: instance_id is now STRING — coerce to str, not int
temporal_smoothing = _coerce_int(temporal_smoothing, "temporal_smoothing", 0)
output_height = _coerce_int(output_height, "output_height", 512)
instance_id = _coerce_int(instance_id, "instance_id", 0)
instance_id = str(instance_id) if instance_id is not None else "0"
padding = _coerce_int(padding, "padding", 0)
cascade = CascadeCache.get(classifier_type)
if cascade is None:
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "ComfyUI_FaceDetectionNode"
description = "H100-optimized face detection with Auto-Padding, Temporal Smoothing, Aspect Ratio Presets, and full batch processing for LTX-Video avatar pipelines. Backward compatible with v1.x workflows."
version = "2.1.1"
version = "2.1.2"
license = {text = "Apache-2.0"}
dependencies = ["opencv-python>=4.5.0", "torch>=2.0.0", "numpy>=1.21.0"]