fix(video): strip 'undefined' from anywhere + one-shot debug log
The previous normalization only caught the EXACT literal "undefined" / "null" / "None" at field-level. Adds two stronger defenses for the case where a sentinel still slips through: 1. _normalize_text_input now also strips surrounding quotes (paste- from-Explorer artifact) and handles embedded newlines by taking the first non-empty non-sentinel line (defends against multi-line Text Multiline inputs with trailing blank or junk lines). 2. New _strip_sentinel_components helper drops any path component that is exactly a sentinel string from the composed effective_ prefix — belt-and-suspenders for any path that somehow carries `\undefined\` as a literal segment by the time it reaches us. Also adds a one-shot print() of fp_raw, od_raw, normalized values, and effective_prefix at the top of combine_video. The user is seeing an undefined/ folder under their custom path despite the field-level normalization, and pure code tracing doesn't reproduce it — the log will surface what kwargs actually contains at run time so we can fix the real cause if defensive stripping doesn't already handle it. To be removed in a follow-up once root cause is known. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
bf60a72a06
commit
943be67fb0
+65
-11
@@ -109,6 +109,9 @@ def _resolve_abs_target(
|
||||
return abs_dir, abs_base
|
||||
|
||||
|
||||
_SENTINEL_STRINGS = ("undefined", "null", "none")
|
||||
|
||||
|
||||
def _normalize_text_input(value) -> str:
|
||||
"""Coerce a ComfyUI STRING widget value to a clean Python string.
|
||||
|
||||
@@ -118,17 +121,60 @@ def _normalize_text_input(value) -> str:
|
||||
/ ``"None"``) instead of ``""``. Treating those as valid input
|
||||
causes nonsensical ``undefined/`` subdirectories on disk.
|
||||
|
||||
Any of those sentinel strings (case-insensitive) become ``""``;
|
||||
everything else is returned ``.strip()``-ed.
|
||||
Also defends against:
|
||||
- Surrounding quotes (users paste paths from Explorer with quotes).
|
||||
- Trailing whitespace including newlines (WAS Text Multiline can
|
||||
leave a trailing newline when the user pressed Enter at the end).
|
||||
- Embedded sentinel components separated by a newline (a multi-line
|
||||
Text Multiline node with a second sentinel-looking line).
|
||||
|
||||
Any sentinel string (case-insensitive, post-strip) becomes ``""``;
|
||||
everything else is the cleaned value.
|
||||
"""
|
||||
if value is None:
|
||||
return ""
|
||||
s = str(value).strip()
|
||||
if s.lower() in ("undefined", "null", "none"):
|
||||
s = str(value).strip().strip('"').strip("'").strip()
|
||||
# If a newline split the value across lines, take the first
|
||||
# non-empty non-sentinel line.
|
||||
if "\n" in s or "\r" in s:
|
||||
for line in s.replace("\r", "\n").split("\n"):
|
||||
line = line.strip()
|
||||
if line and line.lower() not in _SENTINEL_STRINGS:
|
||||
s = line
|
||||
break
|
||||
else:
|
||||
s = ""
|
||||
if s.lower() in _SENTINEL_STRINGS:
|
||||
return ""
|
||||
return s
|
||||
|
||||
|
||||
def _strip_sentinel_components(path: str) -> str:
|
||||
"""Remove any path component that's exactly a sentinel string.
|
||||
|
||||
Belt-and-suspenders defense: if anything upstream of us managed
|
||||
to inject ``undefined`` / ``null`` / ``None`` as a path component
|
||||
(despite the per-field normalization above), drop it before the
|
||||
path reaches the filesystem. Preserves the rest of the path,
|
||||
including the drive root on Windows.
|
||||
|
||||
Returns the path unchanged if no sentinels found, so this is a
|
||||
cheap pass for the common case.
|
||||
"""
|
||||
if not path:
|
||||
return path
|
||||
# Split on whichever separator is present, defaulting to os.sep.
|
||||
sep = "\\" if "\\" in path else "/"
|
||||
parts = path.split(sep)
|
||||
cleaned = [
|
||||
p for p in parts
|
||||
if p == "" or p.lower() not in _SENTINEL_STRINGS
|
||||
]
|
||||
if len(cleaned) == len(parts):
|
||||
return path
|
||||
return sep.join(cleaned)
|
||||
|
||||
|
||||
def _compose_prefix(filename_prefix: str, output_directory: str) -> str:
|
||||
"""Combine ``output_directory`` + ``filename_prefix`` into one path-prefix.
|
||||
|
||||
@@ -231,23 +277,31 @@ if _VHS_AVAILABLE:
|
||||
# the literal string "undefined" for STRING widgets with
|
||||
# empty defaults. Coerce those to "" before any path
|
||||
# composition so we never create an `undefined/` subdir.
|
||||
filename_prefix = _normalize_text_input(
|
||||
kwargs.get("filename_prefix", "AnimateDiff")
|
||||
) or "AnimateDiff"
|
||||
kwargs["filename_prefix"] = filename_prefix
|
||||
output_directory = _normalize_text_input(
|
||||
kwargs.pop("output_directory", "")
|
||||
)
|
||||
fp_raw = kwargs.get("filename_prefix", "AnimateDiff")
|
||||
od_raw = kwargs.pop("output_directory", "")
|
||||
create_path_if_missing = kwargs.pop("create_path_if_missing", False)
|
||||
filename_prefix = _normalize_text_input(fp_raw) or "AnimateDiff"
|
||||
kwargs["filename_prefix"] = filename_prefix
|
||||
output_directory = _normalize_text_input(od_raw)
|
||||
|
||||
# Split-mode: directory + name combined into one effective
|
||||
# prefix that the existing isabs discrimination handles
|
||||
# uniformly. When output_directory is empty this is a no-op.
|
||||
effective_prefix = _compose_prefix(filename_prefix, output_directory)
|
||||
# Belt-and-suspenders: drop any sentinel components that
|
||||
# slipped past per-field normalization (e.g. a path that
|
||||
# already carried `\undefined\` as a literal segment).
|
||||
effective_prefix = _strip_sentinel_components(effective_prefix)
|
||||
if effective_prefix != filename_prefix:
|
||||
kwargs["filename_prefix"] = effective_prefix
|
||||
filename_prefix = effective_prefix
|
||||
|
||||
print(
|
||||
f"[Easy_VideoCombine] fp_raw={fp_raw!r} od_raw={od_raw!r} "
|
||||
f"fp={filename_prefix!r} od={output_directory!r} "
|
||||
f"effective={effective_prefix!r}"
|
||||
)
|
||||
|
||||
target = _resolve_abs_target(filename_prefix, create_path_if_missing)
|
||||
if target is None:
|
||||
return super().combine_video(*args, **kwargs)
|
||||
|
||||
Reference in New Issue
Block a user