Adds a new ARC Save Audio node alongside the existing v1.0.0+ stable
image node. Audio support is alpha — text-prompt moderation only,
audio-content moderation (transcript scanning, voice-clone detection)
is not yet covered and is held for a separate later decision.
The image node (nodes/arc_save.py) is byte-untouched and remains the
trusted production surface. The audio node is isolated in its own file
and the package __init__ is structured so an audio-import failure can
never break image-node registration (verified locally: a simulated
audio crash leaves NODE_CLASS_MAPPINGS = ['ARCSave']).
What the signature attests:
- The CREATION EVENT (when, by whom, against which workflow)
- The platform-attested identity bound to the API key
- The exact bytes-on-disk WAV the node encoded
What the signature does NOT attest:
- That the voice is real (no voice-clone / deepfake detection)
- That the audio contents are factually true or consented to
- That generated audio (TTS, music lyrics) is content-moderated —
only the text PROMPT is moderated
Node design:
- nodes/arc_save_audio.py: ARCSaveAudio class, encodes ComfyUI
AUDIO ({waveform: (B,C,T), sample_rate: int}) to 16-bit PCM WAV
via a hand-rolled writer (no torchaudio dep — only numpy + the
standard tensor.cpu().numpy() path that ComfyUI provides).
- Per-clip loop across the batch (B); same fail-fast posture as
the image node — failure on clip 3 raises, clips 1-2 are kept
and server-side content-hash dedup avoids double-charging.
- Reuses 9 pure helpers from arc_save.py (key loader, prompt
extraction, version detect, retry sentinel, retry wrapper,
output-dir resolution, generation assertion builder). Overrides
only the 4 image-hardcoded paths:
- encode (WAV instead of PNG)
- multipart POST (audio/wav mime; part name stays "image" per
the server-side wire-label decision — server routes by mime)
- download Content-Type guard (audio/* instead of image/*)
- output write (.wav extension; "audio" UI-result key)
- Audio-specific provenance fields baked into the signed assertion:
sample_rate_hz, channels, samples, duration_seconds, format,
encoding. platform_client overridden to "comfyui-arc-save-audio"
so server-side log analytics can distinguish image vs audio
submissions.
Package wiring:
- __init__.py: registers ARCSave (always) + ARCSaveAudio (try/except,
isolated). Audio failure prints a stderr warning and leaves the
image node registered — image is the trusted production surface
and must not be regressed by any audio-side issue.
pyproject.toml: version 1.0.3 -> 1.1.0 (minor bump = additive
feature); description extended with both nodes + the alpha framing.
README.md: alpha framing section added BEFORE setup ("read before
using on real audio") — same honest disclosure pattern the v1.0.3
README uses for prompt moderation and cert trust.
Local verifications:
- Audio encoder: 1-sec 440Hz sine produces a valid 88,244-byte WAV
(identical container shape to the server-side smoke spike at
functions/scripts/c2pa-audio-spike.mjs, which already proved
that exact shape round-trips through c2pa-node sign + Reader).
- Package import: NODE_CLASS_MAPPINGS = ['ARCSave', 'ARCSaveAudio'].
- Audio failure isolation: simulated audio-import crash leaves
NODE_CLASS_MAPPINGS = ['ARCSave']; image node survives.
Operator sequencing requirement: server-side arcIngest must deploy
AUDIO-1 Phase A (commit 49c96d8) BEFORE this node version is published
to PyPI; otherwise installs in the gap will fail with a clean 415
("server-side audio support may not be deployed yet") rather than a
silent failure.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
58 lines
2.3 KiB
Python
58 lines
2.3 KiB
Python
"""
|
|
ComfyUI ARC Save — sign-on-arrival save nodes for ArcVelvetOS.
|
|
|
|
Registers two ComfyUI custom nodes:
|
|
|
|
- ARCSave — image-side, v1.0.0+ stable, terminal/trusted.
|
|
Replaces SaveImage; POSTs PNG to arcIngest,
|
|
writes signed PNG + sidecar receipt.
|
|
|
|
- ARCSaveAudio — audio-side, v1.1.0 alpha. Encodes ComfyUI AUDIO
|
|
to 16-bit PCM WAV, POSTs to arcIngest, writes
|
|
signed WAV + sidecar receipt. Text-prompt
|
|
moderation only — audio-content moderation is
|
|
not yet covered. See nodes/arc_save_audio.py
|
|
module docstring for the full alpha framing.
|
|
|
|
Loading is structured so the audio node is OPTIONAL: if the audio
|
|
module fails to import for any reason (a future torch/numpy/runtime
|
|
incompatibility, an upstream bug, anything), the image node remains
|
|
registered and functional. The image node is the trusted production
|
|
surface and must not be regressed by any audio-side change.
|
|
|
|
See README.md for setup (API key issuance, arc_config.json) and the
|
|
node's wire protocol against arcIngest.
|
|
"""
|
|
|
|
import sys
|
|
|
|
from .nodes.arc_save import (
|
|
NODE_CLASS_MAPPINGS as _IMAGE_NODE_MAPPINGS,
|
|
NODE_DISPLAY_NAME_MAPPINGS as _IMAGE_NODE_DISPLAY_NAMES,
|
|
)
|
|
|
|
NODE_CLASS_MAPPINGS: dict = dict(_IMAGE_NODE_MAPPINGS)
|
|
NODE_DISPLAY_NAME_MAPPINGS: dict = dict(_IMAGE_NODE_DISPLAY_NAMES)
|
|
|
|
# AUDIO-1 — register the audio sibling node. Isolated in a try/except
|
|
# so a failure to import the audio module (e.g. a runtime env without
|
|
# the expected numpy/torch features the audio encoder uses) cannot
|
|
# break the image node's registration. The image node above is the
|
|
# trusted production surface and its availability must not depend on
|
|
# the audio node's import success.
|
|
try:
|
|
from .nodes.arc_save_audio import (
|
|
NODE_CLASS_MAPPINGS as _AUDIO_NODE_MAPPINGS,
|
|
NODE_DISPLAY_NAME_MAPPINGS as _AUDIO_NODE_DISPLAY_NAMES,
|
|
)
|
|
NODE_CLASS_MAPPINGS.update(_AUDIO_NODE_MAPPINGS)
|
|
NODE_DISPLAY_NAME_MAPPINGS.update(_AUDIO_NODE_DISPLAY_NAMES)
|
|
except Exception as e: # noqa: BLE001 — broad on purpose for isolation
|
|
print(
|
|
f"[comfyui-arc-save] ARCSaveAudio node not registered "
|
|
f"(image node remains available): {type(e).__name__}: {e}",
|
|
file=sys.stderr,
|
|
)
|
|
|
|
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
|