Skip to content

Commit 2e43bfd

Browse files
committed
Version 4.21.18
Fix: Restore Numba/librosa JIT compatibility test at startup for NumPy 2.x - Restore full librosa JIT test at startup for NumPy 2.x environments - PR 262 lazy-import optimization had deferred this test too late, after numba was already loaded, making NUMBA_DISABLE_JIT ineffective - NumPy 1.x users still skip the test entirely (zero cost) - Python 3.13+ still handled by preemptive env var (zero cost) - Addresses get_call_template AttributeError crash in librosa on affected hardware
1 parent 3f7e0c2 commit 2e43bfd

6 files changed

Lines changed: 81 additions & 53 deletions

File tree

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [4.21.18] - 2026-02-28
9+
10+
### Added
11+
12+
- Engines like IndexTTS-2, Qwen3-TTS, RVC and others failed with AttributeError
13+
14+
### Changed
15+
16+
- No performance impact for users on NumPy 1.x
17+
18+
### Fixed
19+
20+
- Fix all engines crashing with Numba/librosa JIT error on NumPy 2.x
21+
- Fix crash affecting all TTS engines on NumPy 2.x with certain hardware
22+
- in librosa audio processing (get_call_template error)
23+
- Numba JIT compatibility is now correctly detected and disabled at startup
824
## [4.21.17] - 2026-02-22
925

1026
### Added

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
[![Dynamic TOML Badge][version-shield]][version-url]
88
[![Ko-Fi](https://img.shields.io/badge/Ko--fi-F16061?style=for-the-badge&logo=ko-fi&logoColor=white)](https://ko-fi.com/diogogo)
99

10-
# TTS Audio Suite v4.21.17
10+
# TTS Audio Suite v4.21.18
1111

1212
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/diogogo)
1313

__init__.py

Lines changed: 60 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,25 @@ def _apply_transformers_patches_once():
6969
except Exception as e:
7070
print(f"⚠️ Warning: Could not apply Transformers patches: {e}")
7171

72-
# Numba/Librosa compatibility check DEFERRED to first engine use.
73-
# The numba_compat module imports librosa (~0.7s) to test JIT compilation,
74-
# which is too expensive for startup. Instead, we defer the test until
75-
# an engine actually needs librosa. On Python 3.13+ we preemptively set
76-
# the safe env var since numba JIT is known to be problematic there.
72+
# Numba/Librosa compatibility check at startup.
73+
# On Python 3.13+ we preemptively disable JIT (known incompatible).
74+
# On NumPy 2.x we run the full librosa JIT test (~0.7s) because numba's
75+
# @guvectorize crashes on some hardware with NumPy 2.x — this must happen
76+
# before any engine lazy-loads librosa/numba, or the env var is too late.
77+
# On NumPy 1.x we skip the test entirely (not affected).
7778
if sys.version_info >= (3, 13):
7879
os.environ.setdefault('NUMBA_DISABLE_JIT', '1')
80+
else:
81+
try:
82+
import numpy as _np
83+
if int(_np.__version__.split('.')[0]) >= 2:
84+
numba_compat_path = os.path.join(os.path.dirname(__file__), "utils", "compatibility", "numba_compat.py")
85+
_spec = importlib.util.spec_from_file_location("numba_compat_module", numba_compat_path)
86+
_numba_compat = importlib.util.module_from_spec(_spec)
87+
_spec.loader.exec_module(_numba_compat)
88+
_numba_compat.setup_numba_compatibility(quick_startup=False, verbose=False)
89+
except Exception:
90+
pass
7991

8092
# TorchCodec note: Removed torchcodec dependency to eliminate FFmpeg system requirement
8193
# torchaudio.load() works fine with fallback backends (soundfile, scipy)
@@ -345,10 +357,10 @@ async def get_available_languages_endpoint(request):
345357
# Fallback list
346358
return web.json_response({"languages": ["en", "de", "fr", "ja", "es", "it", "pt", "th", "no"], "error": str(e)})
347359

348-
@PromptServer.instance.routes.post("/api/tts-audio-suite/settings")
349-
async def set_inline_tag_settings_endpoint(request):
350-
"""API endpoint to receive settings from frontend for inline edit tags and restore VC"""
351-
print("🔧 Settings endpoint called") # Immediate print to verify endpoint is reached
360+
@PromptServer.instance.routes.post("/api/tts-audio-suite/settings")
361+
async def set_inline_tag_settings_endpoint(request):
362+
"""API endpoint to receive settings from frontend for inline edit tags and restore VC"""
363+
print("🔧 Settings endpoint called") # Immediate print to verify endpoint is reached
352364
try:
353365
data = await request.json()
354366
precision = data.get("precision", "auto")
@@ -375,45 +387,45 @@ async def set_inline_tag_settings_endpoint(request):
375387
edit_post_processor_module.set_inline_tag_settings(precision=precision, device=device, vc_engine=vc_engine, cosyvoice_variant=cosyvoice_variant)
376388

377389
return web.json_response({"status": "success", "precision": precision, "device": device, "vc_engine": vc_engine, "cosyvoice_variant": cosyvoice_variant})
378-
except Exception as e:
379-
print(f"⚠️ Error setting inline tag settings: {e}")
380-
return web.json_response({"status": "error", "error": str(e)})
381-
382-
@PromptServer.instance.routes.get("/api/tts-audio-suite/voice-preview")
383-
async def get_voice_preview_endpoint(request):
384-
"""
385-
Stream selected Character Voices dropdown audio for browser preview playback.
386-
387-
Query params:
388-
- voice_name: exact dropdown key from get_available_voices()
389-
"""
390-
try:
391-
voice_name = request.query.get("voice_name", "").strip()
392-
if not voice_name or voice_name == "none":
393-
return web.json_response({"error": "voice_name is required and cannot be 'none'"}, status=400)
394-
395-
# Load voice discovery directly by file path to avoid package import issues
396-
voice_discovery_path = os.path.join(os.path.dirname(__file__), "utils", "voice", "discovery.py")
397-
spec = importlib.util.spec_from_file_location("voice_discovery_module", voice_discovery_path)
398-
voice_discovery_module = importlib.util.module_from_spec(spec)
399-
spec.loader.exec_module(voice_discovery_module)
400-
401-
# Use cached discovery for fast preview playback.
402-
voice_discovery_module.get_available_voices(force_refresh=False)
403-
audio_path, _ = voice_discovery_module.load_voice_reference(voice_name)
404-
405-
if not audio_path or not os.path.exists(audio_path):
406-
return web.json_response({"error": f"Voice file not found: {voice_name}"}, status=404)
407-
408-
# Direct stream of resolved local audio file.
409-
response = web.FileResponse(path=audio_path)
410-
response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate"
411-
return response
412-
except Exception as e:
413-
print(f"⚠️ Error serving voice preview audio: {e}")
414-
return web.json_response({"error": str(e)}, status=500)
415-
except Exception as e:
416-
print(f"⚠️ Could not setup API routes: {e}")
390+
except Exception as e:
391+
print(f"⚠️ Error setting inline tag settings: {e}")
392+
return web.json_response({"status": "error", "error": str(e)})
393+
394+
@PromptServer.instance.routes.get("/api/tts-audio-suite/voice-preview")
395+
async def get_voice_preview_endpoint(request):
396+
"""
397+
Stream selected Character Voices dropdown audio for browser preview playback.
398+
399+
Query params:
400+
- voice_name: exact dropdown key from get_available_voices()
401+
"""
402+
try:
403+
voice_name = request.query.get("voice_name", "").strip()
404+
if not voice_name or voice_name == "none":
405+
return web.json_response({"error": "voice_name is required and cannot be 'none'"}, status=400)
406+
407+
# Load voice discovery directly by file path to avoid package import issues
408+
voice_discovery_path = os.path.join(os.path.dirname(__file__), "utils", "voice", "discovery.py")
409+
spec = importlib.util.spec_from_file_location("voice_discovery_module", voice_discovery_path)
410+
voice_discovery_module = importlib.util.module_from_spec(spec)
411+
spec.loader.exec_module(voice_discovery_module)
412+
413+
# Use cached discovery for fast preview playback.
414+
voice_discovery_module.get_available_voices(force_refresh=False)
415+
audio_path, _ = voice_discovery_module.load_voice_reference(voice_name)
416+
417+
if not audio_path or not os.path.exists(audio_path):
418+
return web.json_response({"error": f"Voice file not found: {voice_name}"}, status=404)
419+
420+
# Direct stream of resolved local audio file.
421+
response = web.FileResponse(path=audio_path)
422+
response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate"
423+
return response
424+
except Exception as e:
425+
print(f"⚠️ Error serving voice preview audio: {e}")
426+
return web.json_response({"error": str(e)}, status=500)
427+
except Exception as e:
428+
print(f"⚠️ Could not setup API routes: {e}")
417429

418430
# Setup API routes when extension loads
419431
setup_api_routes()

nodes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
pass
1212

1313
# Version and constants
14-
VERSION = "4.21.17"
14+
VERSION = "4.21.18"
1515
IS_DEV = False # Set to False for release builds
1616
VERSION_DISPLAY = f"v{VERSION}" + (" (dev)" if IS_DEV else "")
1717
SEPARATOR = "=" * 70

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
22
name = "tts_audio_suite"
33
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."
4-
version = "4.21.17"
4+
version = "4.21.18"
55
license = {file = "LICENSE"}
66

77
[project.urls]

utils/compatibility/numba_compat.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def test_numba_compatibility(self, quick_test: bool = True) -> Dict[str, Any]:
5050
if sys.version_info >= (3, 13):
5151
results['jit_compatible'] = False
5252
results['errors'].append("Python 3.13+ detected - numba JIT known incompatible (skipped librosa test)")
53-
53+
5454
results['test_duration'] = time.time() - start_time
5555
self._test_results = results
5656
return results
@@ -111,7 +111,7 @@ def apply_jit_workaround(self) -> None:
111111
numba.config.ENABLE_CUDASIM = True
112112
except ImportError:
113113
pass # numba not imported yet, environment variable will handle it
114-
114+
115115
self._jit_disabled = True
116116
print("🔧 Applied numba JIT workaround for Python 3.12+/Numpy 2.x compatibility")
117117

0 commit comments

Comments
 (0)