The Script Director gets a Generate section for GPT-SoVITS characters
(ui_script_run.js): it queues the script as an API prompt with this
page's client id (audio_tts_run.js), shows queued/running/progress,
plays the result and saves it to output/audio/<character>/. The canvas
is never touched. A line card's retake button adds [take:N] to that
line so only it is regenerated; "New take of all" changes the seed.
Cancel deletes a queued job or interrupts it by prompt id when running
(checked against /queue, since the start event can arrive first).
Events that arrive before /prompt answers are buffered and replayed.
Language/speed start from the character's `defaults` and can be saved
back (read-modify-write, unknown fields kept). Needs Anomalous_TTS
interface 11; older versions get a hint and keep push-to-node.
The Arona multi-voice template read a file only one machine has; the
button, route, helper (alignedVoiceValue) and strings are removed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sending a Prompt Note with a UNet main model chained its LoRAs through
LoraLoader, whose clip input nothing could feed. UNet LoRAs now use
LoraLoaderModelOnly and the editor says to connect a text encoder.
Models without a baseModel in their metadata matched no base model, so
they never appeared; base_models now reports them and
compatible_models serves them for "__unlabeled__". Both routes share
one walker and run it off the event loop.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saving a workflow recipe failed for any workflow with a model loader:
recipe_images.py called _resolve_exact_model_reference and hashlib, which
stayed behind in recipe_schema.py when 6ad975c split the module. The
resolver moves to path_utils.py so both recipe modules can import it
without a cycle.
The same split left media_routes.py without time/tempfile (card
thumbnails silently fell back to full-size covers) and model_metadata.py
without _resolve_paths_to_model_info_sync (editing a moved model failed).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- audio_engines.js: F5-TTS and GPT-SoVITS (the separate Anomalous_TTS node
pack), detected through /object_info/<node class>. Both engines' voices are
normalised into one voice-group shape so cards, sidebar and Script Director
share one implementation.
- The audio page shows an engine switch; a missing engine stays listed as
"not installed" with install steps (no popups). The F5-TTS library still
works without the F5-TTS node; GPT-SoVITS is never scanned when missing.
- GPT-SoVITS cards come only from the node's HTTP contract: preview reference
clips, drag a character onto AnomalousTTS_CharacterSpeech, and edit the
emotion references ("Edit emotions"), saved through /anomalous_tts/settings
with unknown fields preserved and the node's validation messages shown.
- Drop targets and the Script Director are per engine: voices of one engine
are refused on the other engine's node with the reason.
- Audio gallery reads the character of GPT-SoVITS generations.
- Usage guide (in-app and docs/guides/audio-studio.md) covers both engines.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W411Z69FC3em9CzpeQEub5
Conflicts:
- api/__init__.py, web/styles.css: keep audio routes/styles and add the
version manager.
- README.md, CHANGELOG.md: take main's released text; this branch's
0922b27 was an earlier alignment of the same v1.57 notes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W411Z69FC3em9CzpeQEub5
- api/version_manager.py: installed version (local only), published
releases on request (GitHub API, git tag fallback), switch to a
published tag, return to the default branch (fast-forward only), undo
the last switch; refuses to overwrite modified tracked files
- header version chip and "Version & updates" panel; nothing goes online
until "Check for updates" is clicked; restart via ComfyUI Manager
- zh/en strings, styles, architecture notes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W411Z69FC3em9CzpeQEub5
Conflicts resolved:
- entry trigger files, web/main.js, api/__init__.py: main carries the squashed
entry fix (09b06d1), identical to this branch before its later cleanup, so
this branch's versions are kept
- ARCHITECTURE.md, docs/architecture/backend.md, web/modules/ui_sidebar.js:
both sides' additions kept
Verified on the merged tree: 107 Python tests, all JS tests except the orphaned
material_parameter_cross_node.mjs, jsdom walkthroughs of the audio studio,
domain switching and the gallery search blocks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W411Z69FC3em9CzpeqEub5
Splitting the query on spaces hid how it was read and made phrases like
"cyberpunk city" impossible to search.
- ui_search_chips.js: Enter or a comma (also "," / "、" inserted by an IME)
turns the typed text into a block; blocks keep their spaces and match as a
phrase; hash-like and numeric blocks are labelled; × removes a block,
Backspace in an empty box removes the last one, one button clears all; text
still being typed joins the search after a short pause; IME composition
never commits a block
- gallery_images accepts repeated `term` params (q still works)
Verified: tests/test_image_search.py (phrase blocks vs split query, 107 Python
tests in total) and a jsdom walkthrough of blocks, IME commas, backspace,
remove, clear and the request parameters.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W411Z69FC3em9CzpeqEub5
- api/image_search.py reads only the PNG text chunks before IDAT (ComfyUI
prompt/workflow, A1111 parameters), builds one small record per image and
caches it by mtime; all space-separated terms must match
- hex terms of 8+ characters also match SHA256 values recorded in the
workflow (extra.anomalous_hashes) and local model files with that hash, so
renamed models are still found; A1111 "Model hash" text matches as text
- gallery_images accepts q; the search box above the gallery debounces input,
shows the match count, keeps paging/infinite scroll and the detail viewer on
the filtered list, and lets a newer query supersede one still loading
On the local output (774 PNG) the first search reads ~10 MB of text chunks;
later searches use the cache.
Verified: tests/test_image_search.py (prompt, seed, AND terms, A1111, file
names, recorded and local-index hashes, cache, chunk reader), full Python suite
(106), jsdom walkthrough of the gallery search box.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W411Z69FC3em9CzpeqEub5
Since 9cebdbd9 model_resolution.py used MODEL_EXTENSIONS without importing it.
/anomalous/all_hashes swallowed the NameError and always returned {}, and
resolve_hash / resolve_hash_batch (recipe model matching) failed while
collecting candidates. The all-hashes walk is now collect_model_hash_index()
so the output gallery search can reuse it; the endpoint behaves as before.
Verified: tests/test_image_search.py::ModelResolutionImportTests; on the local
library the index now returns 191 keys (176 with SHA256) instead of 0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W411Z69FC3em9CzpeqEub5
The gallery listed file names only, and audio from Preview Audio nodes (the
template used one) lives in ComfyUI's temp folder, so the gallery stayed empty.
- api/audio_metadata.py reads ComfyUI's `prompt` comment from FLAC and
Ogg/Opus files (read-only) and extracts the TTS node's speech, sample, seed
- gallery rows show the speech (emotion chips, expandable per-segment script),
voice and seed, with copy-speech; results are cached by mtime/size
- search (?q=) matches speech text and file names
- "unsaved previews" section lists temp audio with "save to gallery", which
copies into output/audio/ (O_EXCL, never overwrites; the preview is untouched)
- audio_stream serves input/output/temp only; .opus is recognized
- parseTaggedSpeech in audio_script.js reads speech back the way F5-TTS splits it
Also outside the repo: ComfyUI/arona_multivoice_workflow.json now ends in a
Save Audio node (prefix audio/F5-TTS/Arona) instead of Preview Audio.
Verified: 100 Python tests (5 new gallery tests with synthetic FLAC/Opus),
tests/audio_script_rules.mjs, jsdom walkthrough (speech rows, preview save,
search, delete, paging), and a read-only run on the two real temp previews.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W411Z69FC3em9CzpeqEub5
Keeps the planned All-in-One loader and cross-node prompt features but removes
the regressions they introduced:
- model references: one adapter table for verified layouts (easy a1111Loader,
easy fullLoader, Efficient Loader) shared by recipe_identity.js and
recipe_schema.py; native loaders keep their stored labels; no more guessing
categories from file names (a "...vae.safetensors" checkpoint was a VAE);
"None"/"Baked VAE" are never references
- recipe summary sync follows the extraction rules: only checkpoint/UNet
loaders or ckpt_name/unet_name set the base model (not an upscaler's
model_name), only sampler-like nodes set seed/steps/cfg, and LoRA edits
update the entry of the edited loader instead of falling back to loras[0]
- one LoRA entry per loader again (deduplication shifted entries)
- prompt injection never writes negative text into a positive slot or the
reverse; the block chooser follows the same rule
- material cards only accept cross-node drops that carry prompts; parameter
blocks stay same-type
- drop unused imports and the unused modelSpecs alias
Verified: regression cases added to tests/recipe_all_in_one_compatibility.mjs
and tests/material_prompt_cross_node.mjs (both fail on the previous commit),
new tests/test_recipe_model_references.py; all JS tests except the orphaned
material_parameter_cross_node.mjs and 95 Python tests pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W411Z69FC3em9CzpeqEub5
ComfyUI-F5-TTS resolves {happy} to <sample stem>.happy.wav beside the selected
sample, so `Arona_happy.wav` + `{Arona_happy}` could never run. Voices now use
Character.wav (main, {main}) and Character.<emotion>.wav ({emotion}); the
catalog groups a main voice with its variants and marks tags that F5-TTS
cannot resolve (missing main, missing .txt, different format).
- script director: one character per script, untagged lines pinned to {main},
pushes sample (main voice, node's own combo spelling) and speech together,
confirms before overwriting and re-checks the node after the dialog
- pure rules (splitting, composing, planning, combo spelling) in audio_script.js
- one module instance per file: drop ?v= imports that duplicated
ui_audio_gallery/ui_audio_studio state, so studio playback stops the gallery
- all user/file-derived text goes through textContent (no innerHTML)
- studio/sidebar/gallery renders ignore stale responses; errors are shown
- gallery: count badge no longer overwrites the title, paging via "load more",
deletion errors surfaced, timeupdate instead of a 100ms timer
- sidebar: filter selection updates in place instead of refetching; the header
tabs, sidebar and domain switch share browser.switchAudioTab
- uploader: createViewScope lifecycle (Esc/overlay/close release listeners and
object URLs, Esc no longer reaches the parent), 409 -> confirm -> overwrite,
sends the pre-romanization text as original_text, no hard-coded character
- workflow template: confirm before replacing the canvas, clear message when
the template file is missing, align sample paths to the node's combo values
- static styles moved from inline JS into 11-audio-studio.css; strings localized
Verified: tests/audio_script_rules.mjs, tests/test_audio_catalog.py (15), a
jsdom walkthrough of studio/gallery/director/uploader with a stubbed app
(outside the repo), JS syntax and import/export link check. Not yet checked in
a live ComfyUI session.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W411Z69FC3em9CzpeqEub5
- upload: validate target folder with resolve_within, refuse to overwrite an
existing voice unless overwrite=1, stage audio/transcripts and swap them in
together so conversion or disk failures keep the previous voice, drop stale
.txt/.orig.txt and other-format siblings, write .orig.txt for the native script
- ffmpeg is resolved explicitly (PATH, then portable python dir) with a timeout
- voice/gallery scans run in a worker thread; stems are unique across scan
folders; audio URLs are percent-encoded and versioned by mtime
- stream and delete only accept audio extensions; paging and template names
are validated; errors are JSON
- romanizer no longer reads Han-only (Chinese) text as Japanese and reports
missing pykakasi / hangul-romanize instead of silently succeeding
- extension static files use Cache-Control: no-cache (revalidate) instead of
no-store; document the deliberate PATH exception in __init__.py
Verified: new local tests/test_audio_catalog.py (14 tests) plus the existing
Python suite (78 tests) pass in a Linux venv; not yet exercised in ComfyUI.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W411Z69FC3em9CzpeqEub5