From 6b601632f6ebc05bc5f1993ea2b48e835880c7ce Mon Sep 17 00:00:00 2001 From: Jason Hoku Date: Wed, 4 Nov 2026 01:29:46 -0800 Subject: [PATCH] fix(upscale): deferred upscales (run_upscales_at_end) no longer skip every image with 'no filename in manifest' Generated manifest items carry only a /view URL in 'file'; the deferred phase read 'filename', which only upscaled and scanned items have, so every deferred job was skipped. New pure helper manifest_utils.item_filename (filename key, else the URL's filename= parameter) is used there; the dashboard upscale path already parsed the URL. Found by the wave-2 live base-dims check on the 3070. Co-Authored-By: Claude Fable 5.1 --- generation_orchestrator.py | 5 +++-- manifest_utils.py | 17 +++++++++++++++ tests/conftest.py | 1 + tests/test_item_filename.py | 43 +++++++++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 tests/test_item_filename.py diff --git a/generation_orchestrator.py b/generation_orchestrator.py index d00c3fe..997f2ed 100644 --- a/generation_orchestrator.py +++ b/generation_orchestrator.py @@ -17,7 +17,7 @@ from comfy.model_management import InterruptProcessingException from .trigger_words import collect_unique_prompts_with_triggers, build_prompt_with_triggers, clear_trigger_caches from .batch_encoding import batch_encode_prompts, encode_prompt_with_combinators, pinned_clip, DEFAULT_TE_BATCH_SIZE -from .manifest_utils import load_existing_manifest, save_manifest +from .manifest_utils import load_existing_manifest, save_manifest, item_filename from .model_loader import ( load_checkpoint, load_loras, cleanup_model_references, get_latent_channels, get_latent_downscale_ratio, make_empty_latent, load_loras_for_preencoding, @@ -442,7 +442,8 @@ def run_deferred_upscales( print(f"[GridTester] ⚠️ Deferred upscale #{job_idx+1}: base image not found in manifest, skipping") continue - base_filename = base_item.get("filename") + # generated items carry only a /view URL ("file"); upscaled/scanned ones also have "filename" + base_filename = item_filename(base_item) if not base_filename: print(f"[GridTester] ⚠️ Deferred upscale #{job_idx+1}: no filename in manifest, skipping") continue diff --git a/manifest_utils.py b/manifest_utils.py index 68dba2d..422f477 100644 --- a/manifest_utils.py +++ b/manifest_utils.py @@ -336,6 +336,23 @@ def append_items(manifest_path, items, remove_ids=(), session_name=None, default return data +def item_filename(item): + """The image file name of a manifest item: its "filename" key when present, else the ``filename=`` query + parameter of its ``file`` (/view?...) URL, URL-decoded. Generated items only carry ``file``; upscaled and + scanned items also carry ``filename``. Returns "" when neither is usable.""" + if not isinstance(item, dict): + return "" + name = item.get("filename") + if name: + return str(name) + url = item.get("file") or "" + m = re.search(r"[?&]filename=([^&]+)", str(url)) + if not m: + return "" + from urllib.parse import unquote + return unquote(m.group(1)) + + def base_dims_of(item): """{"base_width","base_height"} of the generated image an upscale of `item` starts from ({} if unknown).""" bw, bh = item.get("base_width"), item.get("base_height") diff --git a/tests/conftest.py b/tests/conftest.py index 3de3111..5914197 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -109,6 +109,7 @@ _GEN_ORCH_DEPS = { "save_manifest": lambda *a, **kw: None, "preserve_disk_comparisons": lambda *a, **kw: None, "base_dims_of": lambda item: {}, + "item_filename": lambda item: (item or {}).get("filename", ""), "update_manifest_comparisons": lambda *a, **kw: {"comparisons": [], "added": 0, "removed": 0, "total": 0}, }, "model_loader": { diff --git a/tests/test_item_filename.py b/tests/test_item_filename.py new file mode 100644 index 0000000..4084e76 --- /dev/null +++ b/tests/test_item_filename.py @@ -0,0 +1,43 @@ +"""manifest_utils.item_filename: generated items carry only a /view URL, upscaled/scanned ones a filename. +The deferred-upscale phase (run_upscales_at_end) skipped every image with "no filename in manifest" before it +used this helper; the source-scan test pins that call.""" +import importlib.util +import os +import re + +_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +def _load(name, modname): + spec = importlib.util.spec_from_file_location(modname, os.path.join(_ROOT, name)) + m = importlib.util.module_from_spec(spec) + m.__package__ = os.path.basename(_ROOT) + spec.loader.exec_module(m) + return m + + +mu = _load("manifest_utils.py", "mu_item_filename") + + +def test_filename_key_wins(): + assert mu.item_filename({"filename": "img_1.webp", "file": "/view?filename=other.webp"}) == "img_1.webp" + + +def test_parsed_from_view_url(): + url = "/view?filename=img_179378440462348.webp&type=output&subfolder=benchmarks/S/images" + assert mu.item_filename({"file": url}) == "img_179378440462348.webp" + assert mu.item_filename({"file": "/view?type=output&filename=a%20b.png&subfolder=x"}) == "a b.png" + + +def test_unusable_items(): + assert mu.item_filename({}) == "" + assert mu.item_filename({"file": "/view?type=output"}) == "" + assert mu.item_filename(None) == "" + + +def test_deferred_upscales_use_the_helper(): + src = open(os.path.join(_ROOT, "generation_orchestrator.py"), encoding="utf-8").read() + start = src.index("def run_deferred_upscales(") + body = src[start:start + 6000] + assert "item_filename(base_item)" in body + assert not re.search(r'base_item\.get\("filename"\)', body)