Avoid TorchCodec for tensor-native inference
This commit is contained in:
@@ -27,6 +27,8 @@ python ComfyUI-SAM-Audio/install.py
|
||||
|
||||
Then restart ComfyUI. If installed through ComfyUI Manager, the installer should run automatically.
|
||||
|
||||
The node uses ComfyUI's decoded media tensors and does not install or load TorchCodec.
|
||||
|
||||
## Model download
|
||||
|
||||
Select a model in **SAM-Audio Model Loader** and queue the workflow. The loader first tries Meta's official repository. If Hugging Face denies gated access, it automatically downloads the matching pinned and checksum-verified checkpoint from the [public mirror collection](https://huggingface.co/collections/mrfakename/sam-audio).
|
||||
|
||||
@@ -13,7 +13,6 @@ dependencies = [
|
||||
"regex",
|
||||
"safetensors",
|
||||
"timm",
|
||||
"torchcodec",
|
||||
"torchdiffeq",
|
||||
"tqdm",
|
||||
"transformers>=4.54.0",
|
||||
|
||||
@@ -6,7 +6,6 @@ huggingface-hub
|
||||
regex
|
||||
safetensors
|
||||
timm
|
||||
torchcodec
|
||||
torchdiffeq
|
||||
tqdm
|
||||
transformers>=4.54.0
|
||||
|
||||
@@ -13,6 +13,7 @@ import torch
|
||||
|
||||
from .attention import validate_attention_backend
|
||||
from .audiotools_compat import audiotools_import_compatibility
|
||||
from .torchcodec_compat import torchcodec_import_compatibility
|
||||
from .xformers_compat import xformers_import_compatibility
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
@@ -305,14 +306,17 @@ def import_sam_audio() -> tuple[Any, Any]:
|
||||
LOGGER.info(
|
||||
"Loading SAM-Audio through isolated inference-only compatibility layers"
|
||||
)
|
||||
with xformers_import_compatibility(), audiotools_import_compatibility():
|
||||
with (
|
||||
xformers_import_compatibility(),
|
||||
audiotools_import_compatibility(),
|
||||
torchcodec_import_compatibility(),
|
||||
):
|
||||
module = importlib.import_module("sam_audio")
|
||||
return module.SAMAudio, module.SAMAudioProcessor
|
||||
except Exception as error:
|
||||
raise RuntimeError(
|
||||
"SAM-Audio could not be imported. Install this node's requirements with "
|
||||
"ComfyUI's Python interpreter, then restart ComfyUI. This node does not "
|
||||
"require or install xFormers."
|
||||
"ComfyUI's Python interpreter, then restart ComfyUI."
|
||||
) from error
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
"""Import-only TorchCodec surface for tensor-native ComfyUI inference."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import sys
|
||||
import types
|
||||
from collections.abc import Iterator
|
||||
|
||||
|
||||
class _TensorInputsOnly:
|
||||
def __init__(self, *_args, **_kwargs):
|
||||
raise RuntimeError(
|
||||
"TorchCodec file decoding is disabled in ComfyUI-SAM-Audio. Pass decoded "
|
||||
"ComfyUI AUDIO, IMAGE, and MASK tensors to the node instead."
|
||||
)
|
||||
|
||||
|
||||
class AudioDecoder(_TensorInputsOnly):
|
||||
"""Placeholder for an upstream file-I/O path unused by the ComfyUI node."""
|
||||
|
||||
|
||||
class VideoDecoder(_TensorInputsOnly):
|
||||
"""Placeholder for an upstream file-I/O path unused by the ComfyUI node."""
|
||||
|
||||
|
||||
class AudioEncoder(_TensorInputsOnly):
|
||||
"""Placeholder for an optional upstream ranker disabled by this integration."""
|
||||
|
||||
|
||||
def _compatibility_modules() -> dict[str, types.ModuleType]:
|
||||
torchcodec = types.ModuleType("torchcodec")
|
||||
torchcodec.__path__ = []
|
||||
decoders = types.ModuleType("torchcodec.decoders")
|
||||
encoders = types.ModuleType("torchcodec.encoders")
|
||||
decoders.AudioDecoder = AudioDecoder
|
||||
decoders.VideoDecoder = VideoDecoder
|
||||
encoders.AudioEncoder = AudioEncoder
|
||||
torchcodec.decoders = decoders
|
||||
torchcodec.encoders = encoders
|
||||
return {
|
||||
"torchcodec": torchcodec,
|
||||
"torchcodec.decoders": decoders,
|
||||
"torchcodec.encoders": encoders,
|
||||
}
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def torchcodec_import_compatibility() -> Iterator[None]:
|
||||
"""Temporarily provide upstream's unused decoder imports without loading FFmpeg."""
|
||||
compatibility_modules = _compatibility_modules()
|
||||
missing = object()
|
||||
previous = {
|
||||
name: sys.modules.get(name, missing) for name in compatibility_modules
|
||||
}
|
||||
sys.modules.update(compatibility_modules)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
for name, module in previous.items():
|
||||
if module is missing:
|
||||
sys.modules.pop(name, None)
|
||||
else:
|
||||
sys.modules[name] = module
|
||||
+48
-1
@@ -1,9 +1,15 @@
|
||||
import sys
|
||||
import types
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
from sam_audio_comfy import audiotools_compat, xformers_compat
|
||||
from sam_audio_comfy import (
|
||||
audiotools_compat,
|
||||
torchcodec_compat,
|
||||
xformers_compat,
|
||||
)
|
||||
|
||||
|
||||
def test_node_does_not_require_xformers():
|
||||
@@ -21,6 +27,17 @@ def test_node_does_not_require_xformers():
|
||||
assert not any("xformers" in line for line in dependency_lines)
|
||||
|
||||
|
||||
def test_node_does_not_require_torchcodec():
|
||||
root = Path(__file__).parents[1]
|
||||
for filename in ("requirements.txt", "pyproject.toml"):
|
||||
dependency_lines = [
|
||||
line
|
||||
for line in (root / filename).read_text().lower().splitlines()
|
||||
if line.strip() and not line.lstrip().startswith("#")
|
||||
]
|
||||
assert not any("torchcodec" in line for line in dependency_lines)
|
||||
|
||||
|
||||
def test_upstream_installer_uses_no_deps():
|
||||
root = Path(__file__).parents[1]
|
||||
installer = (root / "install.py").read_text().lower()
|
||||
@@ -58,3 +75,33 @@ def test_audiotools_compatibility_is_import_only_and_scoped(monkeypatch):
|
||||
assert audiotools.ml.BaseModel.INTERN == []
|
||||
|
||||
assert "audiotools" not in sys.modules
|
||||
|
||||
|
||||
def test_torchcodec_compatibility_is_import_only_and_scoped(monkeypatch):
|
||||
monkeypatch.delitem(sys.modules, "torchcodec", raising=False)
|
||||
monkeypatch.delitem(sys.modules, "torchcodec.decoders", raising=False)
|
||||
monkeypatch.delitem(sys.modules, "torchcodec.encoders", raising=False)
|
||||
|
||||
with torchcodec_compat.torchcodec_import_compatibility():
|
||||
decoders = sys.modules["torchcodec.decoders"]
|
||||
assert decoders.AudioDecoder is torchcodec_compat.AudioDecoder
|
||||
assert decoders.VideoDecoder is torchcodec_compat.VideoDecoder
|
||||
with pytest.raises(RuntimeError, match="decoded ComfyUI"):
|
||||
decoders.AudioDecoder("input.wav")
|
||||
|
||||
assert "torchcodec" not in sys.modules
|
||||
assert "torchcodec.decoders" not in sys.modules
|
||||
|
||||
|
||||
def test_existing_torchcodec_modules_are_restored(monkeypatch):
|
||||
original = types.ModuleType("torchcodec")
|
||||
original_decoders = types.ModuleType("torchcodec.decoders")
|
||||
monkeypatch.setitem(sys.modules, "torchcodec", original)
|
||||
monkeypatch.setitem(sys.modules, "torchcodec.decoders", original_decoders)
|
||||
|
||||
with torchcodec_compat.torchcodec_import_compatibility():
|
||||
assert sys.modules["torchcodec"] is not original
|
||||
assert sys.modules["torchcodec.decoders"] is not original_decoders
|
||||
|
||||
assert sys.modules["torchcodec"] is original
|
||||
assert sys.modules["torchcodec.decoders"] is original_decoders
|
||||
|
||||
Reference in New Issue
Block a user