Release v1.0.4: The Compatibility Update

- Added robust CPU Fallback for AMD/Non-CUDA GPUs.
- Implemented Lazy Load for AI Engine to prevent startup crashes.
- Added explicit DLL injection for Cublas/Cudnn on Windows.
- Added Corrupt Model Auto-Repair logic.
- Includes pre-compiled v1.0.4 executable.
This commit is contained in:
Your Name
2026-01-25 20:28:01 +02:00
parent aed489dd23
commit 3137770742
7 changed files with 198 additions and 41 deletions

View File

@@ -21,7 +21,7 @@ except ImportError:
torch = None
# Import directly - valid since we are now running in the full environment
from faster_whisper import WhisperModel
class WhisperTranscriber:
"""
@@ -62,13 +62,32 @@ class WhisperTranscriber:
# Force offline if path exists to avoid HF errors
local_only = new_path.exists()
self.model = WhisperModel(
model_input,
device=device,
compute_type=compute,
download_root=str(get_models_path()),
local_files_only=local_only
)
try:
from faster_whisper import WhisperModel
self.model = WhisperModel(
model_input,
device=device,
compute_type=compute,
download_root=str(get_models_path()),
local_files_only=local_only
)
except Exception as load_err:
# CRITICAL FALLBACK: If CUDA/cublas fails (AMD/Intel users), fallback to CPU
err_str = str(load_err).lower()
if "cublas" in err_str or "cudnn" in err_str or "library" in err_str or "device" in err_str:
logging.warning(f"CUDA Init Failed ({load_err}). Falling back to CPU...")
self.config.set("compute_device", "cpu") # Update config for persistence/UI
self.current_compute_device = "cpu"
self.model = WhisperModel(
model_input,
device="cpu",
compute_type="int8", # CPU usually handles int8 well with newer extensions, or standard
download_root=str(get_models_path()),
local_files_only=local_only
)
else:
raise load_err
self.current_model_size = size
self.current_compute_device = device
@@ -78,6 +97,32 @@ class WhisperTranscriber:
except Exception as e:
logging.error(f"Failed to load model: {e}")
self.model = None
# Auto-Repair: Detect vocabulary/corrupt errors
err_str = str(e).lower()
if "vocabulary" in err_str or "tokenizer" in err_str or "config.json" in err_str:
# ... existing auto-repair logic ...
logging.warning("Corrupt model detected on load. Attempting to delete and reset...")
try:
import shutil
# Differentiate between simple path and HF path
new_path = get_models_path() / f"faster-whisper-{size}"
if new_path.exists():
shutil.rmtree(new_path)
logging.info(f"Deleted corrupt model at {new_path}")
else:
# Try legacy HF path
hf_path = get_models_path() / f"models--Systran--faster-whisper-{size}"
if hf_path.exists():
shutil.rmtree(hf_path)
logging.info(f"Deleted corrupt HF model at {hf_path}")
# Notify UI to refresh state (will show 'Download' button now)
# We can't reach bridge easily here without passing it in,
# but the UI polls or listens to logs.
# The user will simply see "Model Missing" in settings after this.
except Exception as del_err:
logging.error(f"Failed to delete corrupt model: {del_err}")
def transcribe(self, audio_data, is_file: bool = False, task: Optional[str] = None) -> str:
"""
@@ -89,7 +134,7 @@ class WhisperTranscriber:
if not self.model:
self.load_model()
if not self.model:
return "Error: Model failed to load."
return "Error: Model failed to load. Please check Settings -> Model Info."
try:
# Config
@@ -174,8 +219,11 @@ class WhisperTranscriber:
def model_exists(self, size: str) -> bool:
"""Checks if a model size is already downloaded."""
new_path = get_models_path() / f"faster-whisper-{size}"
if (new_path / "config.json").exists():
return True
if new_path.exists():
# Strict check
required = ["config.json", "model.bin", "vocabulary.json"]
if all((new_path / f).exists() for f in required):
return True
# Legacy HF cache check
folder_name = f"models--Systran--faster-whisper-{size}"

View File

@@ -381,25 +381,24 @@ class UIBridge(QObject):
# Check new simple format used by DownloadWorker
path_simple = get_models_path() / f"faster-whisper-{size}"
if path_simple.exists() and any(path_simple.iterdir()):
return True
if path_simple.exists():
# Strict check: Ensure all critical files exist
required = ["config.json", "model.bin", "vocabulary.json"]
if all((path_simple / f).exists() for f in required):
return True
# Check HF Cache format (legacy/default)
folder_name = f"models--Systran--faster-whisper-{size}"
path_hf = get_models_path() / folder_name
snapshots = path_hf / "snapshots"
if snapshots.exists() and any(snapshots.iterdir()):
return True
# Check direct folder (simple)
path_direct = get_models_path() / size
if (path_direct / "config.json").exists():
return True
return True # Legacy cache structure is complex, assume valid if present
return False
except Exception as e:
logging.error(f"Error checking model status: {e}")
return False
return False
@Slot(str)
def downloadModel(self, size):