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:
@@ -228,3 +228,6 @@ ignore/
|
||||
|
||||
# Test output files
|
||||
tests/output/
|
||||
|
||||
# TTS Audio Suite settings cache (user preferences)
|
||||
.inline_tag_settings_cache.json
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
[![Dynamic TOML Badge][version-shield]][version-url]
|
||||
[](https://ko-fi.com/diogogo)
|
||||
|
||||
# TTS Audio Suite v4.19.1
|
||||
# TTS Audio Suite v4.19.2
|
||||
|
||||
[](https://ko-fi.com/diogogo)
|
||||
|
||||
|
||||
+17
-9
@@ -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)})
|
||||
|
||||
@@ -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
@@ -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]
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user