Files
IxMxAMAR bb6efd22a6 v2.1.0: audit + 11 new utility nodes + 101 tests
Big audit + fix release driven by two full-codebase Gemini Pro reviews
and first-party verification against the live ElevenLabs API spec.

Security
- Scrub API keys from every raised error (URL params, JSON detail, conn
  errors). Defense against leaking into ComfyUI logs / workflow PNGs.
- AccountInfo redacts PII (email, name, billing IDs) by default and now
  uses /v1/user/subscription (no PII).
- Download size cap (500MB default) — was unbounded BytesIO.
- Multipart file pointers reset between retries (was sending empty body
  on attempt 2).
- SaveAudio sanitizes filename + subdir against ../ traversal.

Fixed — Credit-burning bugs
- IS_CHANGED=NaN removed from paid nodes; replaced with InputCacheMixin
  that hashes (inputs minus api_key) so identical re-queues don't re-bill.
- VoiceClone / VoiceCreate require explicit create=True (was uploading
  on every queue, burning voice slots).

Fixed — API contract bugs (silent failures vs current spec)
- Music: uses music_length_ms (was duration_seconds), 600s max (was
  300), exposes composition_plan, respect_sections_durations,
  store_for_inpainting, sign_with_c2pa, model_id=music_v1.
- SFX: model_id=eleven_text_to_sound_v2 (was eleven_sfx_v2 — invalid).
- TTS: forces apply_text_normalization=off for eleven_v3 (only valid
  value per spec); raises early if turbo/flash_v2_5 lacks language_code.
- STT: actually sends timestamps_granularity=none when chosen.
- TTS: malformed pronunciation_dictionary_locators raises instead of
  silently dropping.

Fixed — Audio I/O
- PCM decode pads odd byte count so truncated streams don't ValueError.
- audio_to_comfy returns 1s silence on empty body (was crashing).
- Fallback silence is 1s @ 44.1kHz, not [1,1,1] (downstream STFT was
  asserting window_size).
- comfy_audio_to_bytes accepts 1-D / 2-D / 3-D waveforms.
- NamedTemporaryFile replaced with BytesIO (Windows handle leak + reopen).
- parse_error_response sniffs Content-Type, returns <binary response>
  for non-text bodies (was UnicodeDecodeError on binary 500s).

Fixed — Retry hardening
- Retry-After honors HTTP-date format (RFC 7231).
- Exponential backoff clamped to 60s + ±25% jitter.
- Voice cache bounded LRU (32 entries) — was unbounded growth.
- Pagination hard-cap 100 pages (belt-and-braces).

Added — 11 new utility nodes
GetVoiceByName, VoiceTagInserter, SubtitleExport (SRT/VTT), AudioConcat
(resample + broadcast), AudioNormalize (peak/RMS dBFS), AudioMetadata,
AudioChannels (mono/stereo/strip), AudioTrim, CostEstimator,
VoiceSettingsPreset, SaveAudio (sanitized).

Added — Quality-of-life
- enable_logging toggle (zero-retention mode) on TTS/TTSTimestamps/
  STS/Dialogue.
- FetchVoices: gender filter + count output.
- VoiceDesign: returns all 3 previews (was throwing away the 2 you
  paid for); preview_index selector.
- Dialogue: validates total text length, explicit model dropdown,
  defaults normalization=off (required for v3).
- STT: adds scribe_v1.
- SFX: live duration validation (0.5-30s).
- previous_text/next_text tooltips clarified (context, not output).

Tests: 101/101 passing (was 0). All HTTP mocked — no real API calls.
- test_audio_conversions.py (13) — AUDIO dict shape, PCM round-trip
- test_errors_scrubbing.py (15) — secret-leakage regression, parser
- test_retry.py (18) — backoff, file-pointer reset, size cap
- test_pagination.py (7) — termination + LRU bound
- test_nodes.py (48) — node validation, credit safety, utility nodes

Node count: 15 -> 26. Audit notes in CHANGELOG.md.
2026-05-17 18:16:30 +05:30

71 lines
1.3 KiB
Python

"""Shared utilities for ComfyUI-AI-Suite.
Re-exports everything so services can do:
from ...shared import tensor_to_base64, AlwaysExecuteMixin, api_request_with_retry
"""
from .errors import (
APIError,
APITransientError,
APIPermanentError,
APIQuotaError,
parse_error_response,
scrub_secrets,
)
from .retry import (
api_request_with_retry,
download_file,
)
from .node_utils import (
AlwaysExecuteMixin,
InputCacheMixin,
redact_secrets,
)
from .auth import (
BaseAPIKeyNode,
DualKeyAPIKeyNode,
)
from .conversions import (
tensor_to_pil,
pil_to_tensor,
tensor_to_base64,
tensor_to_jpeg_bytes,
mask_to_jpeg_bytes,
bytes_to_tensor,
audio_to_comfy,
comfy_to_audio_bytes,
)
__all__ = [
# errors
"APIError",
"APITransientError",
"APIPermanentError",
"APIQuotaError",
"parse_error_response",
"scrub_secrets",
# retry
"api_request_with_retry",
"download_file",
# node_utils
"AlwaysExecuteMixin",
"InputCacheMixin",
"redact_secrets",
# auth
"BaseAPIKeyNode",
"DualKeyAPIKeyNode",
# conversions
"tensor_to_pil",
"pil_to_tensor",
"tensor_to_base64",
"tensor_to_jpeg_bytes",
"mask_to_jpeg_bytes",
"bytes_to_tensor",
"audio_to_comfy",
"comfy_to_audio_bytes",
]