Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions app/common/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
)

from app.config import SETTINGS_PATH, WORK_PATH
from app.core.utils.platform_utils import get_available_transcribe_models
from app.core.utils.platform_utils import get_available_transcribe_models, is_windows

from ..core.entities import (
FasterWhisperModelEnum,
Expand Down Expand Up @@ -174,10 +174,13 @@ class Config(QConfig):
)

# ------------------- Faster Whisper 配置 -------------------
_default_faster_whisper_program = (
"faster-whisper-xxl.exe" if is_windows() else "faster-whisper-xxl"
)
faster_whisper_program = ConfigItem(
"FasterWhisper",
"Program",
"faster-whisper-xxl.exe",
_default_faster_whisper_program,
)
faster_whisper_model = OptionsConfigItem(
"FasterWhisper",
Expand Down
62 changes: 51 additions & 11 deletions app/components/FasterWhisperSettingWidget.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import os
import platform
import shutil
import subprocess
from pathlib import Path

Expand Down Expand Up @@ -44,8 +46,7 @@
from app.thread.file_download_thread import FileDownloadThread
from app.thread.modelscope_download_thread import ModelscopeDownloadThread

# 在文件开头添加常量定义
FASTER_WHISPER_PROGRAMS = [
WINDOWS_FASTER_WHISPER_PROGRAMS = [
{
"label": "GPU(cuda) + CPU 版本",
"value": "faster-whisper-gpu.7z",
Expand All @@ -62,6 +63,21 @@
},
]

LINUX_FASTER_WHISPER_PROGRAMS = [
{
"label": "GPU(cuda) + CPU 版本",
"value": "Faster-Whisper-XXL_r245.4_linux.7z",
"type": "GPU",
"size": "1.30 GB",
"downloadLink": "https://github.com/Purfview/whisper-standalone-win/releases/download/Faster-Whisper-XXL/Faster-Whisper-XXL_r245.4_linux.7z",
}
]

if platform.system() == "Linux":
FASTER_WHISPER_PROGRAMS = LINUX_FASTER_WHISPER_PROGRAMS
else:
FASTER_WHISPER_PROGRAMS = WINDOWS_FASTER_WHISPER_PROGRAMS

FASTER_WHISPER_MODELS = [
{
"label": "Tiny",
Expand Down Expand Up @@ -127,24 +143,37 @@ def check_faster_whisper_exists() -> tuple[bool, list[str]]:
"""检查 faster-whisper 程序是否存在

检查以下两种情况:
1. bin目录下是否有 faster-whisper.exe
2. bin目录下是否有 Faster-Whisper-XXL/faster-whisper-xxl.exe
1. PATH/本地目录是否有 faster-whisper(.exe)
2. PATH/本地目录是否有 faster-whisper-xxl(.exe)

Returns:
tuple[bool, list[str]]: (是否存在程序, 已安装的版本列表)
"""
bin_path = Path(BIN_PATH)
installed_versions = []

# 检查 faster-whisper.exe(CPU版本)
if (bin_path / "faster-whisper.exe").exists():
# 检查 faster-whisper(CPU版本)
cpu_candidates = [
bin_path / "faster-whisper",
bin_path / "faster-whisper.exe",
]
has_cpu = any(p.exists() for p in cpu_candidates) or bool(shutil.which("faster-whisper"))
if has_cpu:
installed_versions.append("CPU")

# 检查 Faster-Whisper-XXL/faster-whisper-xxl.exe(GPU版本)
xxl_path = bin_path / "Faster-Whisper-XXL" / "faster-whisper-xxl.exe"
if xxl_path.exists():
# 检查 faster-whisper-xxl(GPU版本)
xxl_candidates = [
bin_path / "faster-whisper-xxl",
bin_path / "faster-whisper-xxl.exe",
bin_path / "Faster-Whisper-XXL" / "faster-whisper-xxl",
bin_path / "Faster-Whisper-XXL" / "faster-whisper-xxl.exe",
]
has_xxl = any(p.exists() for p in xxl_candidates) or bool(
shutil.which("faster-whisper-xxl")
)
if has_xxl:
installed_versions.extend(["GPU", "CPU"])
installed_versions = list(set(installed_versions))
installed_versions = sorted(set(installed_versions))

return bool(installed_versions), installed_versions

Expand Down Expand Up @@ -457,7 +486,7 @@ def _on_program_download_finished(self, save_path):
"""程序下载完成处理"""
try:
# 检查是否是 CPU 版本的直接下载
if save_path.endswith(".exe"):
if save_path.lower().endswith(".exe"):
# 如果是exe文件,重命名为faster-whisper.exe
os.rename(save_path, os.path.join(BIN_PATH, "faster-whisper.exe"))
self._finish_program_installation()
Expand Down Expand Up @@ -633,6 +662,17 @@ def _open_program_folder(self):

def _finish_program_installation(self):
"""完成程序安装"""
# Linux 可执行文件下载后需要补充执行权限
if platform.system() == "Linux":
for candidate in [
Path(BIN_PATH) / "faster-whisper-xxl",
Path(BIN_PATH) / "faster-whisper",
Path(BIN_PATH) / "Faster-Whisper-XXL" / "faster-whisper-xxl",
Path(BIN_PATH) / "Faster-Whisper-XXL" / "faster-whisper",
]:
if candidate.exists():
candidate.chmod(candidate.stat().st_mode | 0o111)

InfoBar.success(
self.tr("安装完成"),
self.tr("Faster Whisper 程序已安装成功"),
Expand Down
154 changes: 131 additions & 23 deletions app/core/asr/faster_whisper.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import GPUtil

from ...config import BIN_PATH
from ..utils.logger import setup_logger
from ..utils.subprocess_helper import StreamReader
from .asr_data import ASRData, ASRDataSeg
Expand Down Expand Up @@ -96,21 +97,115 @@ def __init__(
self.one_word = 0
self.sentence = True

# 根据设备选择程序
if self.device == "cpu":
if shutil.which("faster-whisper-xxl"):
self.faster_whisper_program = "faster-whisper-xxl"
else:
if not shutil.which("faster-whisper"):
raise EnvironmentError("faster-whisper程序未找到,请确保已经下载。")
self.faster_whisper_program = "faster-whisper"
self.vad_method = ""
elif self.device == "cuda":
if not shutil.which("faster-whisper-xxl"):
raise EnvironmentError(
"faster-whisper-xxl 程序未找到,请确保已经下载。"
)
self.faster_whisper_program = "faster-whisper-xxl"
# 根据设备和平台自动解析程序路径(支持 Linux/Windows、本地 bin 目录和 PATH)
self.faster_whisper_program = self._resolve_program(
preferred_program=faster_whisper_program,
device=self.device,
)

# CPU 下如果退化到普通 faster-whisper,关闭 xxl 才支持的 VAD 方法参数
if self.device == "cpu" and not self._is_xxl_program(self.faster_whisper_program):
self.vad_method = ""

@staticmethod
def _is_xxl_program(program: str) -> bool:
return "faster-whisper-xxl" in Path(program).name.lower()

@staticmethod
def _is_error_line(line: str) -> bool:
lower_line = line.lower()
return (
"error" in lower_line
or "failed" in lower_line
or "traceback" in lower_line
or "exception" in lower_line
)

@staticmethod
def _detect_glibc_error(line: str) -> bool:
lower_line = line.lower()
return "glibc_" in lower_line and "not found" in lower_line

@staticmethod
def _candidate_paths(name: str) -> List[Path]:
"""Generate local filesystem candidates from a command/program name."""
if not name:
return []

candidate = Path(name)
paths: List[Path] = []

# Absolute or relative path from config/user
if candidate.is_absolute() or candidate.parent != Path("."):
paths.append(candidate)
if not str(candidate).lower().endswith(".exe"):
paths.append(Path(f"{candidate}.exe"))

# Common local locations in this project
local_names = [name]
if not name.lower().endswith(".exe"):
local_names.append(f"{name}.exe")

for local_name in local_names:
paths.append(BIN_PATH / local_name)
paths.append(BIN_PATH / "Faster-Whisper-XXL" / local_name)

return paths

@classmethod
def _resolve_existing_program(cls, name: str) -> Optional[str]:
"""Resolve executable by checking PATH and local bin folders."""
if not name:
return None

# PATH lookup first
which_path = shutil.which(name)
if which_path:
return which_path

# Also try .exe on non-Windows when config keeps old value
if not name.lower().endswith(".exe"):
which_exe = shutil.which(f"{name}.exe")
if which_exe:
return which_exe

for candidate in cls._candidate_paths(name):
if candidate.exists():
return str(candidate)

return None

@classmethod
def _resolve_program(cls, preferred_program: str, device: str) -> str:
"""Resolve usable faster-whisper executable path for target device."""
if device == "cuda":
candidate_names = [
preferred_program,
"faster-whisper-xxl",
"faster-whisper-xxl.exe",
]
else:
candidate_names = [
preferred_program,
"faster-whisper-xxl",
"faster-whisper-xxl.exe",
"faster-whisper",
"faster-whisper.exe",
]

# Keep order while removing empty/duplicated values
deduped_names = list(dict.fromkeys([name for name in candidate_names if name]))

for name in deduped_names:
resolved = cls._resolve_existing_program(name)
if resolved:
return resolved

if device == "cuda":
raise EnvironmentError(
"faster-whisper-xxl 程序未找到,请先下载 Linux/Windows 对应版本。"
)
raise EnvironmentError("faster-whisper 程序未找到,请确保已经下载。")

def _build_command(self, audio_input: str) -> List[str]:
"""Build command line arguments for faster-whisper."""
Expand Down Expand Up @@ -155,9 +250,7 @@ def _build_command(self, audio_input: str) -> List[str]:
cmd.extend(["--vad_filter", "false"])

# 人声分离
if self.ff_mdx_kim2 and self.faster_whisper_program.startswith(
"faster-whisper-xxl"
):
if self.ff_mdx_kim2 and self._is_xxl_program(self.faster_whisper_program):
cmd.append("--ff_mdx_kim2")

# 文本处理参数
Expand Down Expand Up @@ -264,6 +357,7 @@ def _default_callback(x, y):

is_finish = False
error_msg = ""
known_runtime_error = ""
last_progress = 0

# 实时处理输出
Expand All @@ -274,8 +368,14 @@ def _default_callback(x, y):
for stream_name, line in reader.get_remaining_output():
line = line.strip()
if line:
if "error" in line:
error_msg += line
if self._is_error_line(line):
error_msg += f"{line}\n"
if self._detect_glibc_error(line):
known_runtime_error = (
"Faster-Whisper-XXL 与当前系统 GLIBC 不兼容。"
"请重新运行 run.sh 自动切换兼容版本,"
"或在环境变量 FASTER_WHISPER_XXL_LINUX_URL 指定兼容包。"
)
else:
logger.info(line)
break
Expand All @@ -299,15 +399,23 @@ def _default_callback(x, y):
if "Subtitles are written to" in line:
is_finish = True
callback(*ASRStatus.COMPLETED.callback_tuple())
if "error" in line or "Error" in line:
error_msg += line
if self._is_error_line(line):
error_msg += f"{line}\n"
if self._detect_glibc_error(line):
known_runtime_error = (
"Faster-Whisper-XXL 与当前系统 GLIBC 不兼容。"
"请重新运行 run.sh 自动切换兼容版本,"
"或在环境变量 FASTER_WHISPER_XXL_LINUX_URL 指定兼容包。"
)
logger.error(line)
else:
logger.info(line)

if not is_finish:
logger.error("Faster Whisper 错误: %s", error_msg)
raise RuntimeError(error_msg)
if known_runtime_error:
raise RuntimeError(known_runtime_error)
raise RuntimeError(error_msg.strip() or "Faster Whisper 运行失败")

# 判断是否识别成功
if not output_path.exists():
Expand Down
Loading