代码初始化
This commit is contained in:
@@ -19,10 +19,18 @@ var (
|
||||
DetectedWhisperPath string
|
||||
)
|
||||
|
||||
// EnsureDependencies 启动时检查并安装 ffmpeg 和 whisper
|
||||
func EnsureDependencies(ctx context.Context) {
|
||||
func init() {
|
||||
ensureDependencies()
|
||||
}
|
||||
|
||||
// ensureDependencies 启动时检查并安装 ffmpeg 和 whisper
|
||||
func ensureDependencies() {
|
||||
ctx := context.Background()
|
||||
g.Log().Info(ctx, "========== 检查依赖环境 ==========")
|
||||
|
||||
// 打印当前运行环境信息
|
||||
g.Log().Infof(ctx, "平台: %s/%s, Docker: %v", runtime.GOOS, runtime.GOARCH, isRunningInContainer())
|
||||
|
||||
ensureFFmpeg(ctx)
|
||||
ensureWhisper(ctx)
|
||||
resolveWhisperPath(ctx)
|
||||
@@ -35,6 +43,26 @@ func EnsureDependencies(ctx context.Context) {
|
||||
g.Log().Info(ctx, "===================================")
|
||||
}
|
||||
|
||||
// isRunningInContainer 检测是否运行在 Docker 容器中
|
||||
func isRunningInContainer() bool {
|
||||
// 方法1: 检查 /.dockerenv 文件
|
||||
if _, err := os.Stat("/.dockerenv"); err == nil {
|
||||
return true
|
||||
}
|
||||
// 方法2: 检查 /proc/1/cgroup 是否包含 docker 关键字
|
||||
if data, err := os.ReadFile("/proc/1/cgroup"); err == nil {
|
||||
if strings.Contains(string(data), "docker") ||
|
||||
strings.Contains(string(data), "kubepods") ||
|
||||
strings.Contains(string(data), "containerd") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// inContainer 是否为容器环境(简化调用)
|
||||
var inContainer = isRunningInContainer()
|
||||
|
||||
// ensureFFmpeg 确保 ffmpeg 可用
|
||||
func ensureFFmpeg(ctx context.Context) {
|
||||
if _, err := exec.LookPath("ffmpeg"); err == nil {
|
||||
@@ -46,52 +74,147 @@ func ensureFFmpeg(ctx context.Context) {
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
// 检查是否安装了 Homebrew
|
||||
if _, err := exec.LookPath("brew"); err != nil {
|
||||
g.Log().Warningf(ctx, "[ffmpeg] ⚠ 未检测到 Homebrew,请手动安装:\n brew install ffmpeg")
|
||||
return
|
||||
}
|
||||
cmd := exec.CommandContext(ctx, "brew", "install", "ffmpeg")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "[ffmpeg] ❌ 安装失败: %v\n%s", err, string(output))
|
||||
return
|
||||
}
|
||||
g.Log().Info(ctx, "[ffmpeg] ✔ 安装成功")
|
||||
installFFmpegOnMac(ctx)
|
||||
|
||||
case "linux":
|
||||
// 尝试 apt
|
||||
if _, err := exec.LookPath("apt"); err == nil {
|
||||
cmd := exec.CommandContext(ctx, "sudo", "apt", "install", "-y", "ffmpeg")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "[ffmpeg] ❌ apt 安装失败: %v\n%s", err, string(output))
|
||||
return
|
||||
}
|
||||
g.Log().Info(ctx, "[ffmpeg] ✔ 安装成功")
|
||||
return
|
||||
}
|
||||
// 尝试 yum
|
||||
if _, err := exec.LookPath("yum"); err == nil {
|
||||
cmd := exec.CommandContext(ctx, "sudo", "yum", "install", "-y", "ffmpeg")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "[ffmpeg] ❌ yum 安装失败: %v\n%s", err, string(output))
|
||||
return
|
||||
}
|
||||
g.Log().Info(ctx, "[ffmpeg] ✔ 安装成功")
|
||||
return
|
||||
}
|
||||
g.Log().Warningf(ctx, "[ffmpeg] ⚠ 请手动安装: sudo apt install ffmpeg")
|
||||
installFFmpegOnLinux(ctx)
|
||||
|
||||
case "windows":
|
||||
installFFmpegOnWindows(ctx)
|
||||
|
||||
default:
|
||||
g.Log().Warningf(ctx, "[ffmpeg] ⚠ 不支持的平台(%s),请手动安装 ffmpeg", runtime.GOOS)
|
||||
}
|
||||
}
|
||||
|
||||
// installFFmpegOnMac 通过 Homebrew 安装 ffmpeg
|
||||
func installFFmpegOnMac(ctx context.Context) {
|
||||
if _, err := exec.LookPath("brew"); err != nil {
|
||||
g.Log().Warningf(ctx, "[ffmpeg] ⚠ 未检测到 Homebrew,请手动安装:\n brew install ffmpeg")
|
||||
return
|
||||
}
|
||||
cmd := exec.CommandContext(ctx, "brew", "install", "ffmpeg")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "[ffmpeg] ❌ 安装失败: %v\n%s", err, string(output))
|
||||
return
|
||||
}
|
||||
g.Log().Info(ctx, "[ffmpeg] ✔ 安装成功")
|
||||
}
|
||||
|
||||
// installFFmpegOnLinux 在 Linux(含 Docker)上安装 ffmpeg
|
||||
func installFFmpegOnLinux(ctx context.Context) {
|
||||
// Docker 容器通常以 root 运行,不需要 sudo
|
||||
sudoPrefix := ""
|
||||
if !inContainer {
|
||||
// 非容器环境,检查是否需要 sudo
|
||||
if _, err := exec.LookPath("sudo"); err == nil {
|
||||
sudoPrefix = "sudo"
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 尝试 apt (Debian/Ubuntu)
|
||||
if _, err := exec.LookPath("apt-get"); err == nil {
|
||||
args := []string{"install", "-y", "ffmpeg"}
|
||||
if sudoPrefix != "" {
|
||||
args = append([]string{sudoPrefix}, args...)
|
||||
}
|
||||
cmd := exec.CommandContext(ctx, "apt-get", args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "[ffmpeg] ❌ apt-get 安装失败: %v\n%s", err, string(output))
|
||||
return
|
||||
}
|
||||
g.Log().Info(ctx, "[ffmpeg] ✔ 安装成功")
|
||||
// 更新库缓存(Debian/Ubuntu 会用 ldconfig 更新)
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 尝试 apk (Alpine Linux,常见于 Docker 精简镜像)
|
||||
if _, err := exec.LookPath("apk"); err == nil {
|
||||
// Alpine 的 apk 不需要 sudo(默认以 root 运行)
|
||||
cmd := exec.CommandContext(ctx, "apk", "add", "ffmpeg")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "[ffmpeg] ❌ apk 安装失败: %v\n%s", err, string(output))
|
||||
return
|
||||
}
|
||||
g.Log().Info(ctx, "[ffmpeg] ✔ 安装成功")
|
||||
return
|
||||
}
|
||||
|
||||
// 3. 尝试 yum (CentOS/RHEL)
|
||||
if _, err := exec.LookPath("yum"); err == nil {
|
||||
args := []string{"install", "-y", "ffmpeg"}
|
||||
if sudoPrefix != "" {
|
||||
args = append([]string{sudoPrefix}, args...)
|
||||
}
|
||||
cmd := exec.CommandContext(ctx, "yum", args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "[ffmpeg] ❌ yum 安装失败: %v\n%s", err, string(output))
|
||||
return
|
||||
}
|
||||
g.Log().Info(ctx, "[ffmpeg] ✔ 安装成功")
|
||||
return
|
||||
}
|
||||
|
||||
if inContainer {
|
||||
g.Log().Warningf(ctx, "[ffmpeg] ⚠ 容器中未找到 apt-get/apk/yum,请将 ffmpeg 预装在 Docker 镜像中")
|
||||
} else {
|
||||
g.Log().Warningf(ctx, "[ffmpeg] ⚠ 请手动安装: sudo apt-get install ffmpeg")
|
||||
}
|
||||
}
|
||||
|
||||
// installFFmpegOnWindows 在 Windows 上安装 ffmpeg
|
||||
func installFFmpegOnWindows(ctx context.Context) {
|
||||
// 1. 尝试 winget (Windows 10/11 内置)
|
||||
if _, err := exec.LookPath("winget"); err == nil {
|
||||
g.Log().Infof(ctx, "[ffmpeg] 通过 winget 安装...")
|
||||
cmd := exec.CommandContext(ctx, "winget", "install", "--id", "FFmpeg.FFmpeg", "-e", "--accept-package-agreements")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err == nil {
|
||||
g.Log().Info(ctx, "[ffmpeg] ✔ 安装成功")
|
||||
return
|
||||
}
|
||||
g.Log().Warningf(ctx, "[ffmpeg] ⚠ winget 安装失败: %v\n%s", err, string(output))
|
||||
}
|
||||
|
||||
// 2. 尝试 choco (Chocolatey)
|
||||
if _, err := exec.LookPath("choco"); err == nil {
|
||||
// choco 安装可能需要管理员权限
|
||||
g.Log().Infof(ctx, "[ffmpeg] 通过 choco 安装...")
|
||||
cmd := exec.CommandContext(ctx, "choco", "install", "ffmpeg", "-y")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err == nil {
|
||||
g.Log().Info(ctx, "[ffmpeg] ✔ 安装成功")
|
||||
return
|
||||
}
|
||||
g.Log().Warningf(ctx, "[ffmpeg] ⚠ choco 安装失败: %v\n%s", err, string(output))
|
||||
}
|
||||
|
||||
// 3. 尝试 scoop
|
||||
if _, err := exec.LookPath("scoop"); err == nil {
|
||||
g.Log().Infof(ctx, "[ffmpeg] 通过 scoop 安装...")
|
||||
cmd := exec.CommandContext(ctx, "scoop", "install", "ffmpeg")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err == nil {
|
||||
g.Log().Info(ctx, "[ffmpeg] ✔ 安装成功")
|
||||
return
|
||||
}
|
||||
g.Log().Warningf(ctx, "[ffmpeg] ⚠ scoop 安装失败: %v\n%s", err, string(output))
|
||||
}
|
||||
|
||||
g.Log().Warningf(ctx, `[ffmpeg] ⚠ 请手动安装 ffmpeg,推荐方式:
|
||||
1. winget install --id FFmpeg.FFmpeg -e
|
||||
2. choco install ffmpeg -y
|
||||
3. 从 https://ffmpeg.org/download.html 下载并加入 PATH`)
|
||||
}
|
||||
|
||||
// ensureWhisper 确保 whisper 可用(优先安装 C++ 版,速度更快)
|
||||
func ensureWhisper(ctx context.Context) {
|
||||
// 1. 检查是否已有 whisper-cpp(C++ 版,最快)
|
||||
// exec.LookPath 在 Windows 上会自动查找 .exe 后缀
|
||||
if path, err := exec.LookPath("whisper-cpp"); err == nil {
|
||||
g.Log().Infof(ctx, "[whisper] ✔ C++ 版已安装: %s", path)
|
||||
return
|
||||
@@ -101,16 +224,19 @@ func ensureWhisper(ctx context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 检查 Homebrew 安装目录(即使不在 PATH 也能找到)
|
||||
if p := findHomebrewWhisperCpp(); p != "" {
|
||||
DetectedWhisperPath = p
|
||||
// 自动添加到 PATH 环境变量
|
||||
addToShellPath(ctx, filepath.Dir(p))
|
||||
g.Log().Infof(ctx, "[whisper] ✔ C++ 版已安装(自动检测): %s", p)
|
||||
return
|
||||
// 2. 仅在 macOS 上检查 Homebrew 安装目录(即使不在 PATH 也能找到)
|
||||
if runtime.GOOS == "darwin" {
|
||||
if p := findHomebrewWhisperCpp(); p != "" {
|
||||
DetectedWhisperPath = p
|
||||
if !inContainer {
|
||||
addToShellPath(ctx, filepath.Dir(p))
|
||||
}
|
||||
g.Log().Infof(ctx, "[whisper] ✔ C++ 版已安装(自动检测): %s", p)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 尝试安装 whisper-cpp(C++ 版)
|
||||
// 3. 仅在 macOS 上尝试使用 Homebrew 安装 C++ 版
|
||||
if runtime.GOOS == "darwin" {
|
||||
if _, err := exec.LookPath("brew"); err == nil {
|
||||
g.Log().Infof(ctx, "[whisper] 安装 C++ 版 (brew install whisper-cpp)...")
|
||||
@@ -118,9 +244,9 @@ func ensureWhisper(ctx context.Context) {
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err == nil {
|
||||
g.Log().Info(ctx, "[whisper] ✔ C++ 版安装成功")
|
||||
// 装好后把 Homebrew bin 加到 PATH
|
||||
addToShellPath(ctx, getHomebrewBinDir())
|
||||
// 检测安装路径
|
||||
if !inContainer {
|
||||
addToShellPath(ctx, getHomebrewBinDir())
|
||||
}
|
||||
if p := findHomebrewWhisperCpp(); p != "" {
|
||||
DetectedWhisperPath = p
|
||||
}
|
||||
@@ -151,16 +277,25 @@ func ensureWhisper(ctx context.Context) {
|
||||
pipCmd = "pip"
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(ctx, pipCmd, "install", "--user", "openai-whisper")
|
||||
// pip install --user 可能在某些环境下不兼容,尝试先不加 --user,失败后再加
|
||||
cmd := exec.CommandContext(ctx, pipCmd, "install", "openai-whisper")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "[whisper] ❌ pip 安装失败: %v\n%s", err, string(output))
|
||||
return
|
||||
// 尝试 --user 模式
|
||||
g.Log().Warningf(ctx, "[whisper] pip 全局安装失败: %v,尝试 --user 模式...", err)
|
||||
cmd = exec.CommandContext(ctx, pipCmd, "install", "--user", "openai-whisper")
|
||||
output, err = cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "[whisper] ❌ pip 安装失败: %v\n%s", err, string(output))
|
||||
return
|
||||
}
|
||||
}
|
||||
g.Log().Info(ctx, "[whisper] ✔ Python 版安装成功")
|
||||
|
||||
// 安装后自动配置 PATH
|
||||
configureWhisperPath(ctx)
|
||||
// 安装后自动配置 PATH(仅在非容器、非 Windows 环境)
|
||||
if !inContainer && runtime.GOOS != "windows" {
|
||||
configureWhisperPath(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// resolveWhisperPath 自动找到 whisper 二进制路径并存储
|
||||
@@ -174,6 +309,7 @@ func resolveWhisperPath(ctx context.Context) {
|
||||
}
|
||||
|
||||
// 1. 优先检测 C++ 版本(快 3-5 倍)
|
||||
// exec.LookPath 在 Windows 上自动查找 .exe 后缀
|
||||
for _, name := range []string{"whisper-cpp", "whisper-cli"} {
|
||||
if path, err := exec.LookPath(name); err == nil {
|
||||
DetectedWhisperPath = path
|
||||
@@ -182,11 +318,13 @@ func resolveWhisperPath(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 在 Homebrew 目录查找 C++ 版本
|
||||
if p := findHomebrewWhisperCpp(); p != "" {
|
||||
DetectedWhisperPath = p
|
||||
g.Log().Infof(ctx, "[whisper] ✔ C++ 版(自动检测): %s", p)
|
||||
return
|
||||
// 2. 仅在 macOS 上查找 Homebrew 目录下的 C++ 版本
|
||||
if runtime.GOOS == "darwin" {
|
||||
if p := findHomebrewWhisperCpp(); p != "" {
|
||||
DetectedWhisperPath = p
|
||||
g.Log().Infof(ctx, "[whisper] ✔ C++ 版(自动检测): %s", p)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 从 PATH 查找 Python 版 whisper
|
||||
@@ -215,6 +353,10 @@ func getWhisperCandidates() []string {
|
||||
// 通过 python 探针获取 user-site bin 目录
|
||||
if p := getUserPythonBin(); p != "" {
|
||||
candidates = append(candidates, filepath.Join(p, "whisper"))
|
||||
// Windows 上 pip 安装的可执行文件是 .exe
|
||||
if runtime.GOOS == "windows" {
|
||||
candidates = append(candidates, filepath.Join(p, "whisper.exe"))
|
||||
}
|
||||
}
|
||||
|
||||
// 常见 pip user base 路径
|
||||
@@ -233,6 +375,22 @@ func getWhisperCandidates() []string {
|
||||
candidates = append(candidates,
|
||||
filepath.Join(userHome, ".local", "bin", "whisper"),
|
||||
)
|
||||
case "windows":
|
||||
// Windows 上 pip --user 安装的脚本路径
|
||||
candidates = append(candidates,
|
||||
filepath.Join(userHome, "AppData", "Roaming", "Python", "Scripts", "whisper.exe"),
|
||||
filepath.Join(userHome, "AppData", "Roaming", "Python", "Scripts", "whisper"),
|
||||
filepath.Join(userHome, "AppData", "Local", "Programs", "Python", "Scripts", "whisper.exe"),
|
||||
filepath.Join(userHome, "AppData", "Local", "Programs", "Python", "Scripts", "whisper"),
|
||||
)
|
||||
// Python 版本特定路径
|
||||
pythonVersions := []string{"39", "310", "311", "312", "313"}
|
||||
for _, ver := range pythonVersions {
|
||||
candidates = append(candidates,
|
||||
filepath.Join(userHome, "AppData", "Roaming", "Python", "Python"+ver, "Scripts", "whisper.exe"),
|
||||
filepath.Join(userHome, "AppData", "Roaming", "Python", "Python"+ver, "Scripts", "whisper"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return candidates
|
||||
@@ -361,6 +519,17 @@ func addToShellPath(ctx context.Context, dir string) {
|
||||
return
|
||||
}
|
||||
|
||||
// 容器环境不修改 shell 配置(无意义)
|
||||
if inContainer {
|
||||
return
|
||||
}
|
||||
|
||||
// Windows 环境不修改 shell rc 文件(使用系统环境变量)
|
||||
if runtime.GOOS == "windows" {
|
||||
g.Log().Infof(ctx, "[setup] Windows 环境,请手动将 %s 添加到系统 PATH 环境变量", dir)
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否已在 PATH 中
|
||||
currentPath := os.Getenv("PATH")
|
||||
if strings.Contains(currentPath, dir) {
|
||||
|
||||
Reference in New Issue
Block a user