Version 4.19.2

Add configurable VC engine for <restore> tags

Technical implementation:
- Add ComfyUI settings menu option for VC engine selection
- Support three VC engines: ChatterBox 23-Lang, ChatterBox, CosyVoice
- Fix module import to use shared sys.modules instance
- Implement file-based settings cache for persistence across restarts
- Add beforeQueuing hook to ensure settings sync before workflows
This commit is contained in:
diodiogod
2026-01-28 12:18:54 -03:00
parent b13ec81872
commit e840c6df4d
8 changed files with 157 additions and 65 deletions
+3
View File
@@ -228,3 +228,6 @@ ignore/
# Test output files
tests/output/
# TTS Audio Suite settings cache (user preferences)
.inline_tag_settings_cache.json
+10
View File
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [4.19.2] - 2026-01-28
### Added
- Add user-selectable voice conversion engine for <restore> tags
- New ComfyUI settings menu: Voice Restoration (<restore> Tags)
- Choose between ChatterBox 23-Lang (default), ChatterBox, or CosyVoice for voice restoration
- Settings persist across ComfyUI restarts automatically
- CosyVoice option enables native CosyVoice3 VC for seamless workflows
- All engines support the same <restore> tag syntax with iteration control
## [4.19.1] - 2026-01-28
### Added
+1 -1
View File
@@ -7,7 +7,7 @@
[![Dynamic TOML Badge][version-shield]][version-url]
[![Ko-Fi](https://img.shields.io/badge/Ko--fi-F16061?style=for-the-badge&logo=ko-fi&logoColor=white)](https://ko-fi.com/diogogo)
# TTS Audio Suite v4.19.1
# TTS Audio Suite v4.19.2
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/diogogo)
+17 -9
View File
@@ -305,25 +305,33 @@ def setup_api_routes():
@PromptServer.instance.routes.post("/api/tts-audio-suite/settings")
async def set_inline_tag_settings_endpoint(request):
"""API endpoint to receive settings from frontend for inline edit tags"""
"""API endpoint to receive settings from frontend for inline edit tags and restore VC"""
print("🔧 Settings endpoint called") # Immediate print to verify endpoint is reached
try:
data = await request.json()
precision = data.get("precision", "auto")
device = data.get("device", "auto")
vc_engine = data.get("vc_engine", "chatterbox_23lang")
print(f"🔧 Received settings: precision={precision}, device={device}")
print(f"🔧 Received settings: precision={precision}, device={device}, vc_engine={vc_engine}")
# Load edit_post_processor directly by file path to avoid package import issues
edit_post_processor_path = os.path.join(os.path.dirname(__file__), "utils", "audio", "edit_post_processor.py")
spec = importlib.util.spec_from_file_location("edit_post_processor_module", edit_post_processor_path)
edit_post_processor_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(edit_post_processor_module)
# Import edit_post_processor using normal import to ensure we get the same module instance
# that will be used during workflow execution
# CRITICAL: Must use the same module instance, not create a new one via importlib!
try:
from utils.audio import edit_post_processor as edit_post_processor_module
except ImportError:
# Fallback: Load directly by file path if normal import fails
edit_post_processor_path = os.path.join(os.path.dirname(__file__), "utils", "audio", "edit_post_processor.py")
spec = importlib.util.spec_from_file_location("utils.audio.edit_post_processor", edit_post_processor_path)
edit_post_processor_module = importlib.util.module_from_spec(spec)
sys.modules["utils.audio.edit_post_processor"] = edit_post_processor_module # Register in sys.modules!
spec.loader.exec_module(edit_post_processor_module)
# Store in global settings that edit_post_processor can access
edit_post_processor_module.set_inline_tag_settings(precision=precision, device=device)
edit_post_processor_module.set_inline_tag_settings(precision=precision, device=device, vc_engine=vc_engine)
return web.json_response({"status": "success", "precision": precision, "device": device})
return web.json_response({"status": "success", "precision": precision, "device": device, "vc_engine": vc_engine})
except Exception as e:
print(f"⚠️ Error setting inline tag settings: {e}")
return web.json_response({"status": "error", "error": str(e)})
+1 -1
View File
@@ -11,7 +11,7 @@ except ImportError:
pass
# Version and constants
VERSION = "4.19.1"
VERSION = "4.19.2"
IS_DEV = False # Set to False for release builds
VERSION_DISPLAY = f"v{VERSION}" + (" (dev)" if IS_DEV else "")
SEPARATOR = "=" * 70
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "tts_audio_suite"
description = "TTS Audio Suite - Universal multi-engine TTS extension for ComfyUI with unified architecture supporting IndexTTS-2, ChatterBox, Chatterbox Multilingual TTS (Official 23-Lang), F5-TTS, Higgs Audio 2, VibeVoice, and RVC engines. It has character voice management, SRT subtitle TTS support, and audio processing capabilities."
version = "4.19.1"
version = "4.19.2"
license = {file = "LICENSE"}
[project.urls]
+66 -12
View File
@@ -28,23 +28,48 @@ _audio_editor_node = None
# Global settings for inline edit tags (set from ComfyUI settings menu)
_inline_tag_settings = {
"precision": "auto",
"device": "auto"
"device": "auto",
"vc_engine": "chatterbox_23lang" # Default VC engine for <restore> tags
}
# Settings cache file to persist across module reloads
import os
import json
_settings_cache_file = os.path.join(os.path.dirname(__file__), ".inline_tag_settings_cache.json")
def set_inline_tag_settings(precision: str = "auto", device: str = "auto"):
# Load settings from cache on module import
try:
if os.path.exists(_settings_cache_file):
with open(_settings_cache_file, 'r') as f:
cached_settings = json.load(f)
_inline_tag_settings.update(cached_settings)
except Exception:
pass
def set_inline_tag_settings(precision: str = "auto", device: str = "auto", vc_engine: str = "chatterbox_23lang"):
"""
Set global settings for inline edit tag processing.
Set global settings for inline edit tag processing and voice restoration.
Called from the API endpoint when user changes settings in ComfyUI menu.
Args:
precision: Model precision (auto, fp32, fp16, bf16, int8, int4)
device: Device (auto, cuda, cpu, xpu)
vc_engine: Voice conversion engine for <restore> tags (chatterbox_23lang, chatterbox, cosyvoice)
"""
global _inline_tag_settings
_inline_tag_settings["precision"] = precision
_inline_tag_settings["device"] = device
_inline_tag_settings["vc_engine"] = vc_engine
print(f"🎨 Step Audio EditX inline tags: precision={precision}, device={device}")
print(f"🔄 Voice restoration engine: {vc_engine}")
# Save to cache file to persist across module reloads
try:
with open(_settings_cache_file, 'w') as f:
json.dump(_inline_tag_settings, f)
except Exception as e:
print(f"⚠️ Failed to save settings cache: {e}")
def get_inline_tag_settings() -> Dict[str, str]:
@@ -194,22 +219,33 @@ def _apply_edit_via_node(
# Global cache for Voice Changer node (lazy-loaded)
_cached_vc_node = None
def _restore_voice_via_vc(edited_audio_dict, original_voice_dict, iterations=1, language="English"):
def _restore_voice_via_vc(edited_audio_dict, original_voice_dict, iterations=1, language="English", vc_engine=None):
"""
Apply ChatterBox Official 23-Lang Voice Changer to restore original voice.
Apply voice conversion to restore original voice.
Reuses existing UnifiedVoiceChangerNode logic with its cache system.
Supports multiple VC engines:
- chatterbox_23lang: ChatterBox Official 23-Lang (default, 23 languages)
- chatterbox: ChatterBox Classic (English, German, Norwegian)
- cosyvoice: CosyVoice3 native VC (24kHz output, multilingual)
Args:
edited_audio_dict: Audio after edits {waveform, sample_rate}
original_voice_dict: Original voice reference {waveform, sample_rate}
iterations: Number of VC refinement passes (1-5)
language: Language for VC model (e.g., "English", "Polish", etc.)
vc_engine: VC engine to use (defaults to global setting)
Returns:
dict: Restored audio {waveform, sample_rate}
"""
global _cached_vc_node
# Get VC engine from settings if not specified
if vc_engine is None:
settings = get_inline_tag_settings()
vc_engine = settings.get("vc_engine", "chatterbox_23lang")
# Lazy-load VC node instance (cache globally like Audio Editor)
if _cached_vc_node is None:
import os
@@ -234,14 +270,32 @@ def _restore_voice_via_vc(edited_audio_dict, original_voice_dict, iterations=1,
# Instantiate the VC node
_cached_vc_node = vc_module.UnifiedVoiceChangerNode()
# Create engine config for ChatterBox 23-Lang VC
engine_config = {
'engine_type': 'chatterbox_official_23lang',
'config': {
'device': 'auto',
'language': language
# Create engine config based on selected VC engine
if vc_engine == "cosyvoice":
engine_config = {
'engine_type': 'cosyvoice',
'config': {
'device': 'auto',
'mode': 'zero_shot', # Use zero_shot for VC
'speed': 1.0
}
}
elif vc_engine == "chatterbox":
engine_config = {
'engine_type': 'chatterbox',
'config': {
'device': 'auto',
'language': language
}
}
else: # chatterbox_23lang (default)
engine_config = {
'engine_type': 'chatterbox_official_23lang',
'config': {
'device': 'auto',
'language': language
}
}
}
# Call existing VC node logic with refinement passes
result = _cached_vc_node.convert_voice(
+58 -41
View File
@@ -2,6 +2,39 @@ import { app } from '../../scripts/app.js'
const SETTING_CATEGORY = "TTS Audio Suite";
const SETTING_SECTION_STEP_EDITX = "Step Audio EditX (Inline Tags)";
const SETTING_SECTION_RESTORE_VC = "Voice Restoration (<restore> Tags)";
// Shared function to send settings to backend
async function sendSettingsToBackend() {
try {
const precision = app.ui.settings.getSettingValue("TTSAudioSuite.InlineEditTags.Precision", "auto");
const device = app.ui.settings.getSettingValue("TTSAudioSuite.InlineEditTags.Device", "auto");
const vcEngine = app.ui.settings.getSettingValue("TTSAudioSuite.RestoreTags.VCEngine", "chatterbox_23lang");
console.log(`TTS Audio Suite: Sending settings to backend - precision=${precision}, device=${device}, vcEngine=${vcEngine}`);
const response = await fetch("/api/tts-audio-suite/settings", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
precision: precision,
device: device,
vc_engine: vcEngine
})
});
if (!response.ok) {
console.error(`TTS Audio Suite: Failed to send settings to backend (status ${response.status})`);
} else {
const result = await response.json();
console.log("TTS Audio Suite: Backend confirmed settings:", result);
}
} catch (error) {
console.error("TTS Audio Suite: Error sending settings to backend:", error);
}
}
app.registerExtension({
name: "TTS_Audio_Suite.Settings",
@@ -29,63 +62,38 @@ app.registerExtension({
tooltip:
"Device for Step Audio EditX model when using inline edit tags (<Laughter>, <style:whisper>, etc.).\n" +
"Auto selects best available device (cuda > xpu > cpu).\n" +
"Note: This ONLY affects inline edit tags. When using the Step Audio EditX node directly, use the node's device parameter instead.\n\n" +
"Note: This ONLY affects inline edit tags. When using the Step Audio EditX node directly, use the node's precision parameter instead.\n\n" +
"⚠️ IMPORTANT: Refresh browser (F5) after changing settings for them to take effect."
},
{
id: "TTSAudioSuite.RestoreTags.VCEngine",
name: "Voice Conversion Engine for <restore> Tags",
type: "combo",
defaultValue: "chatterbox_23lang",
options: ["chatterbox_23lang", "chatterbox", "cosyvoice"],
category: [SETTING_CATEGORY, SETTING_SECTION_RESTORE_VC, "VC Engine"],
tooltip:
"Voice conversion engine used for <restore> tags in post-processing.\n" +
"ChatterBox 23-Lang (default): Best quality, supports 23 languages\n" +
"ChatterBox: Original engine, English/German/Norwegian\n" +
"CosyVoice: New option with native CosyVoice3 VC (24kHz output)\n\n" +
"⚠️ IMPORTANT: Refresh browser (F5) after changing settings for them to take effect."
}
],
async setup() {
// Send settings to Python backend whenever they change
const sendSettingsToBackend = async () => {
try {
const precision = app.ui.settings.getSettingValue("TTSAudioSuite.InlineEditTags.Precision", "auto");
const device = app.ui.settings.getSettingValue("TTSAudioSuite.InlineEditTags.Device", "auto");
console.log(`TTS Audio Suite: Sending settings to backend - precision=${precision}, device=${device}`);
const response = await fetch("/api/tts-audio-suite/settings", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
precision: precision,
device: device
})
});
if (!response.ok) {
console.error(`TTS Audio Suite: Failed to send settings to backend (status ${response.status})`);
} else {
const result = await response.json();
console.log("TTS Audio Suite: Backend confirmed settings:", result);
}
} catch (error) {
console.error("TTS Audio Suite: Error sending settings to backend:", error);
}
};
// Send settings on initial load (with delay to ensure settings are loaded)
setTimeout(() => {
sendSettingsToBackend();
console.log("TTS Audio Suite: Initial settings sent to backend");
}, 1000);
// Send settings before each prompt execution
app.registerExtension({
name: "TTS_Audio_Suite.Settings.BeforePrompt",
async beforeQueuing() {
await sendSettingsToBackend();
return null;
}
});
// Listen for setting changes and send to backend
const originalChangeSettings = app.ui.settings.setSettingValue;
app.ui.settings.setSettingValue = function(id, value) {
originalChangeSettings.call(this, id, value);
// If it's one of our settings, send to backend immediately
if (id.startsWith("TTSAudioSuite.InlineEditTags.")) {
if (id.startsWith("TTSAudioSuite.InlineEditTags.") || id.startsWith("TTSAudioSuite.RestoreTags.")) {
sendSettingsToBackend();
}
};
@@ -93,3 +101,12 @@ app.registerExtension({
console.log("TTS Audio Suite: Settings registered and synced with backend");
}
})
// Register separate extension for beforeQueuing hook (must be at top level)
app.registerExtension({
name: "TTS_Audio_Suite.Settings.BeforePrompt",
async beforeQueuing() {
await sendSettingsToBackend();
return null;
}
})