Files
Patrick CasinoandClaude Opus 4.7 c63c80151b AUDIO-1 Phase B: v1.1.0 — ARCSaveAudio sibling node (alpha)
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>
2026-06-19 23:47:28 -04:00

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"]