Shot Camera: spaCy model failed to load with preshed 3.0.14 (OverflowError); pin preshed<3.0.14, repair on start, name the real cause in the log

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T81dXrsAyE9Uu8UvYzKvGt
This commit is contained in:
Claude
2026-10-25 16:20:14 +00:00
parent b903648950
commit cce20f136a
4 changed files with 75 additions and 3 deletions
+36 -1
View File
@@ -26,6 +26,10 @@ _lock = threading.Lock()
_SPACY_MODEL = ("en_core_web_sm @ https://github.com/explosion/spacy-models/releases/download/"
"en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl")
# preshed 3.0.14 breaks spaCy's model loading (OverflowError: Python int too large to convert to C
# ssize_t, in Tokenizer.from_disk) on Python 3.13; 3.0.13 loads fine.
PRESHED_PIN = "preshed<3.0.14"
def _nvidia_gpu() -> bool:
"""nvidia-vfx ships wheels for x86-64 Linux and Windows only, and needs a CUDA card."""
@@ -44,7 +48,7 @@ ADDONS = (
"requires": ("nvidia-vfx>=0.2",), "modules": ("nvvfx",), "offered": _nvidia_gpu},
{"id": "camera", "title": "Automatic camera and focus",
"hint": "Lets Shot camera aim camera moves, cuts, transitions and focus at the right things in your prompts, using spaCy.",
"requires": ("spacy>=3.8,<3.9", _SPACY_MODEL), "modules": ("spacy", "en_core_web_sm"), "offered": lambda: True},
"requires": ("spacy>=3.8,<3.9", PRESHED_PIN, _SPACY_MODEL), "modules": ("spacy", "en_core_web_sm"), "offered": lambda: True},
{"id": "lora_training", "title": "LoRA training",
"hint": "Train your own LoRA from your media and rated results, with a tag editor and automatic tagging (onnxruntime). Training runs on ComfyUI's own trainer.",
"requires": ("onnxruntime>=1.17",), "modules": ("onnxruntime",), "offered": lambda: True},
@@ -118,10 +122,41 @@ def set_wanted(addon_id: str, on: bool) -> bool:
return True
def preshed_broken() -> bool:
"""True when the installed preshed is the release that cannot load spaCy models."""
try:
from importlib import metadata
v = tuple(int(x) for x in metadata.version("preshed").split(".")[:3])
except Exception: # noqa: BLE001
return False
return v >= (3, 0, 14)
def repair_spacy() -> bool:
"""Downgrade preshed when spaCy is here with the broken one. Only a restart makes the running
process see it (the compiled module is already loaded), so the log says so."""
if not (importlib.util.find_spec("spacy") and preshed_broken()):
return False
import subprocess
try:
proc = subprocess.run([sys.executable, "-m", "pip", "install", "--disable-pip-version-check", PRESHED_PIN],
capture_output=True, text=True, timeout=300)
except (OSError, subprocess.SubprocessError) as exc:
log.alert("add-ons", f"spaCy needs `pip install \"{PRESHED_PIN}\"` and the automatic try failed: {exc}")
return False
if proc.returncode != 0:
log.alert("add-ons", f"spaCy needs `pip install \"{PRESHED_PIN}\"` and pip failed: {(proc.stderr or proc.stdout)[-300:]}")
return False
log.info("add-ons", "spaCy could not load its model with preshed 3.0.14; installed an older preshed. Restart ComfyUI once.")
return True
def install_wanted() -> None:
"""At ComfyUI start: install what is wanted and missing. Never raises, and never retries a failure
on its own (a broken install would add minutes to every start); toggling the add-on retries."""
try:
if "camera" in _wanted(_read()):
repair_spacy()
with _lock:
data = _read() or {}
failed = dict(data.get("failed") or {})
+12
View File
@@ -127,3 +127,15 @@ def test_lora_training_starts_on_when_its_package_is_already_there(here):
assert next(a["state"] for a in addons.listing() if a["id"] == "lora_training") == "installed"
addons.set_wanted("lora_training", False)
assert not addons.enabled("lora_training")
def test_the_camera_add_on_pins_the_preshed_that_can_load_spacy_models():
camera = next(a for a in addons.ADDONS if a["id"] == "camera")
assert addons.PRESHED_PIN in camera["requires"]
def test_preshed_3_0_14_is_flagged_and_older_is_not(monkeypatch):
from importlib import metadata
for have, bad in (("3.0.14", True), ("3.0.13", False), ("3.1.0", True)):
monkeypatch.setattr(metadata, "version", lambda _n, have=have: have)
assert addons.preshed_broken() is bad
+8 -2
View File
@@ -103,8 +103,14 @@ def _spacy():
import spacy
_nlp = spacy.load("en_core_web_sm")
except Exception as e: # noqa: BLE001
fix = ("spacy not installed, run `pip install spacy`" if isinstance(e, ImportError)
else "model missing, run `python -m spacy download en_core_web_sm`")
if isinstance(e, ImportError):
fix = "spacy not installed, run `pip install spacy`"
elif isinstance(e, OverflowError) and addons.preshed_broken():
fix = f"the installed preshed breaks spaCy's model loading, run `pip install \"{addons.PRESHED_PIN}\"` and restart ComfyUI"
elif isinstance(e, OSError):
fix = "model missing, run `python -m spacy download en_core_web_sm`"
else:
fix = "spaCy could not load its model (see the error above)"
_say("spaCy tagger", e, f"simple word rules used; {fix}")
return _nlp
@@ -479,3 +479,22 @@ def test_a_lone_shot_can_take_a_view_but_shot_one_of_several_still_cannot():
assert sc.view_options("[Shot 1] A cat sits.")[0]["shot"] == 1
_o, several = sc.add_shot_views("[Shot 1] A.\n[Shot 2] B.", seed=1, chance=1.0)
assert [a["shot"] for a in several] == [2]
def test_a_model_that_overflows_on_load_is_named_as_the_preshed_problem_not_a_missing_model(monkeypatch):
"""preshed 3.0.14 raises OverflowError inside spacy.load; the old text blamed a missing model."""
import sys
import types
said = []
monkeypatch.setattr(sc, "_nlp", None)
monkeypatch.setattr(sc, "_tried", False)
monkeypatch.setattr(sc.addons, "enabled", lambda _id: True)
monkeypatch.setattr(sc.addons, "preshed_broken", lambda: True)
monkeypatch.setattr(sc, "_say", lambda what, error, effect: said.append((type(error).__name__, effect)))
fake = types.ModuleType("spacy")
def _load(_name):
raise OverflowError("Python int too large to convert to C ssize_t")
fake.load = _load
monkeypatch.setitem(sys.modules, "spacy", fake)
assert sc._spacy() is None
assert said[0][0] == "OverflowError" and "preshed" in said[0][1] and "model missing" not in said[0][1]