Files
2026-06-22 13:05:00 +08:00

472 lines
15 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package setup
import (
"bytes"
"context"
"os"
"os/exec"
"runtime"
"strings"
"github.com/gogf/gf/v2/frame/g"
)
func init() {
ensureDependencies()
}
// ensureDependencies 启动时检查 ffmpeg 依赖
func ensureDependencies() {
ctx := context.Background()
g.Log().Info(ctx, "========== 检查依赖环境 ==========")
// 打印当前运行环境信息
g.Log().Infof(ctx, "平台: %s/%s, Docker: %v", runtime.GOOS, runtime.GOARCH, isRunningInContainer())
ensureFFmpeg(ctx)
ensurePython3(ctx)
ensureSceneDetect(ctx)
ensureHyperFrames(ctx)
g.Log().Info(ctx, "依赖检查完成,所有依赖已就绪")
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 {
g.Log().Info(ctx, "[ffmpeg] ✔ 已安装")
return
}
if inContainer {
g.Log().Fatalf(ctx, "[ffmpeg] ❌ 容器中未找到 ffmpeg,请在 Dockerfile 中预装: RUN apk add --no-cache ffmpeg")
return
}
g.Log().Infof(ctx, "[ffmpeg] 未找到,尝试自动安装...")
switch runtime.GOOS {
case "darwin":
installFFmpegOnMac(ctx)
case "linux":
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`)
}
// ensureHyperFrames 检查 HyperFrames 是否可用,不可用时自动安装
func ensureHyperFrames(ctx context.Context) {
// 1. 优先检查全局 hyperframes 命令
if path, err := exec.LookPath("hyperframes"); err == nil {
version := getHyperFramesVersion(path)
g.Log().Infof(ctx, "[hyperframes] ✔ 已安装, 版本=%s, 路径=%s", version, path)
return
}
// 2. 检查 npx hyperframes 是否可用
var outBuf bytes.Buffer
checkCmd := exec.Command("npx", "hyperframes", "--version")
checkCmd.Stdout = &outBuf
checkCmd.Stderr = nil
if checkCmd.Run() == nil {
ver := strings.TrimSpace(outBuf.String())
if ver == "" {
ver = "未知"
}
g.Log().Infof(ctx, "[hyperframes] ✔ 已安装(npx), 版本=%s", ver)
return
}
// 3. 未安装,检查 npm/node 可用性再决定是否自动安装
if _, err := exec.LookPath("npm"); err != nil {
if inContainer {
g.Log().Infof(ctx, "[hyperframes] npm 不可用,跳过自动安装(Docker 中请预装 Node.js")
} else {
g.Log().Infof(ctx, "[hyperframes] npm 不可用,跳过自动安装(请先安装 Node.js: https://nodejs.org")
}
return
}
if inContainer {
g.Log().Fatalf(ctx, "[hyperframes] ❌ 容器中未找到 hyperframes,请在 Dockerfile 中预装: RUN npm install -g hyperframes")
return
}
g.Log().Infof(ctx, "[hyperframes] 未找到,尝试自动安装 (npm install -g hyperframes)...")
installCmd := exec.Command("npm", "install", "-g", "hyperframes")
output, err := installCmd.CombinedOutput()
if err != nil {
g.Log().Warningf(ctx, "[hyperframes] ⚠ 自动安装失败: %v\n%s", err, string(output))
g.Log().Infof(ctx, "[hyperframes] 请手动安装: npm install -g hyperframes")
return
}
g.Log().Info(ctx, "[hyperframes] ✔ 自动安装成功")
// 安装后检查路径
if path, err := exec.LookPath("hyperframes"); err == nil {
version := getHyperFramesVersion(path)
g.Log().Infof(ctx, "[hyperframes] 路径=%s, 版本=%s", path, version)
}
}
// getHyperFramesVersion 获取 HyperFrames 版本号
func getHyperFramesVersion(path string) string {
var outBuf bytes.Buffer
vCmd := exec.Command(path, "--version")
vCmd.Stdout = &outBuf
if vCmd.Run() == nil {
return strings.TrimSpace(outBuf.String())
}
return "未知"
}
// ensurePython3 确保 Python3 可用
func ensurePython3(ctx context.Context) {
// 优先检查 python3
if path, err := exec.LookPath("python3"); err == nil {
version := getPythonVersion(ctx, path)
g.Log().Infof(ctx, "[python3] ✔ 已安装, 版本=%s, 路径=%s", version, path)
return
}
// 回退检查 python(部分系统用 python 指向 python3
if path, err := exec.LookPath("python"); err == nil {
version := getPythonVersion(ctx, path)
if strings.HasPrefix(version, "3.") {
g.Log().Infof(ctx, "[python3] ✔ 已安装(python), 版本=%s, 路径=%s", version, path)
return
}
// Python 2,不满足需求
g.Log().Infof(ctx, "[python3] 检测到 python=%s, 需要 Python 3,尝试自动安装...", version)
} else {
if inContainer {
g.Log().Fatalf(ctx, "[python3] ❌ 容器中未找到 python3,请在 Dockerfile 中预装: RUN apk add --no-cache python3 py3-pip")
return
}
g.Log().Infof(ctx, "[python3] 未找到,尝试自动安装...")
}
installPython3(ctx)
}
// ensureSceneDetect 确保 scenedetect 库可用
func ensureSceneDetect(ctx context.Context) {
// 检查 python3 是否可导入 scenedetect
checkCmd := exec.Command("python3", "-c", "import scenedetect; print(scenedetect.__version__)")
var outBuf bytes.Buffer
checkCmd.Stdout = &outBuf
checkCmd.Stderr = nil
if checkCmd.Run() == nil {
ver := strings.TrimSpace(outBuf.String())
if ver == "" {
ver = "未知"
}
g.Log().Infof(ctx, "[scenedetect] ✔ 已安装, 版本=%s", ver)
return
}
if inContainer {
g.Log().Fatalf(ctx, "[scenedetect] ❌ 容器中未找到 scenedetect,请在 Dockerfile 中预装: RUN pip3 install --break-system-packages scenedetect[opencv-headless,ffmpeg]")
return
}
g.Log().Infof(ctx, "[scenedetect] 未找到,尝试自动安装 (pip install scenedetect[opencv,ffmpeg])...")
installCmd := exec.Command("pip3", "install", "scenedetect[opencv,ffmpeg]")
output, err := installCmd.CombinedOutput()
if err != nil {
g.Log().Warningf(ctx, "[scenedetect] ⚠ pip3 安装失败: %v\n%s", err, string(output))
g.Log().Infof(ctx, "[scenedetect] 尝试使用 pip 安装...")
installCmd = exec.Command("pip", "install", "scenedetect[opencv,ffmpeg]")
output, err = installCmd.CombinedOutput()
if err != nil {
g.Log().Warningf(ctx, "[scenedetect] ⚠ pip 安装也失败: %v\n%s", err, string(output))
if inContainer {
g.Log().Infof(ctx, "[scenedetect] Docker 提示: pip 安装失败可能是因为缺少系统级依赖(如 libopencv-dev)。")
g.Log().Infof(ctx, "[scenedetect] 建议在 Dockerfile 中预装: apt-get install -y python3-opencv ffmpeg && pip install scenedetect")
} else {
g.Log().Infof(ctx, "[scenedetect] 请手动安装: pip install 'scenedetect[opencv,ffmpeg]'")
}
return
}
}
g.Log().Info(ctx, "[scenedetect] ✔ 安装成功")
// 验证安装
verifyCmd := exec.Command("python3", "-c", "import scenedetect; print(scenedetect.__version__)")
outBuf.Reset()
verifyCmd.Stdout = &outBuf
if verifyCmd.Run() == nil {
g.Log().Infof(ctx, "[scenedetect] 验证通过, 版本=%s", strings.TrimSpace(outBuf.String()))
}
}
// getPythonVersion 获取 Python 版本号
func getPythonVersion(ctx context.Context, pythonPath string) string {
var outBuf bytes.Buffer
vCmd := exec.CommandContext(ctx, pythonPath, "--version")
vCmd.Stdout = &outBuf
vCmd.Stderr = &outBuf
if vCmd.Run() == nil {
return strings.TrimPrefix(strings.TrimSpace(outBuf.String()), "Python ")
}
return "未知"
}
// installPython3 根据平台自动安装 Python3
func installPython3(ctx context.Context) {
switch runtime.GOOS {
case "darwin":
if _, err := exec.LookPath("brew"); err == nil {
g.Log().Infof(ctx, "[python3] 通过 brew 安装...")
cmd := exec.CommandContext(ctx, "brew", "install", "python@3")
output, err := cmd.CombinedOutput()
if err != nil {
g.Log().Warningf(ctx, "[python3] ⚠ brew 安装失败: %v\n%s", err, string(output))
g.Log().Infof(ctx, "[python3] 请手动安装: brew install python@3")
} else {
g.Log().Info(ctx, "[python3] ✔ 安装成功")
}
} else {
g.Log().Warningf(ctx, "[python3] ⚠ 未检测到 Homebrew,请手动安装 Python3:\n https://www.python.org/downloads/")
}
case "linux":
sudoPrefix := ""
if !inContainer {
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", "python3", "python3-pip"}
if sudoPrefix != "" {
args = append([]string{sudoPrefix}, args...)
}
cmd := exec.CommandContext(ctx, "apt-get", args...)
output, err := cmd.CombinedOutput()
if err != nil {
g.Log().Warningf(ctx, "[python3] ⚠ apt-get 安装失败: %v\n%s", err, string(output))
} else {
g.Log().Info(ctx, "[python3] ✔ 安装成功")
}
return
}
// 2. apk (Alpine)
if _, err := exec.LookPath("apk"); err == nil {
cmd := exec.CommandContext(ctx, "apk", "add", "python3", "py3-pip")
output, err := cmd.CombinedOutput()
if err != nil {
g.Log().Warningf(ctx, "[python3] ⚠ apk 安装失败: %v\n%s", err, string(output))
} else {
g.Log().Info(ctx, "[python3] ✔ 安装成功")
}
return
}
// 3. yum (CentOS/RHEL)
if _, err := exec.LookPath("yum"); err == nil {
args := []string{"install", "-y", "python3", "python3-pip"}
if sudoPrefix != "" {
args = append([]string{sudoPrefix}, args...)
}
cmd := exec.CommandContext(ctx, "yum", args...)
output, err := cmd.CombinedOutput()
if err != nil {
g.Log().Warningf(ctx, "[python3] ⚠ yum 安装失败: %v\n%s", err, string(output))
} else {
g.Log().Info(ctx, "[python3] ✔ 安装成功")
}
return
}
if inContainer {
g.Log().Warningf(ctx, "[python3] ⚠ 容器中未找到包管理器,请将 python3 预装在 Docker 镜像中")
} else {
g.Log().Warningf(ctx, "[python3] ⚠ 请手动安装: sudo apt-get install python3 python3-pip")
}
case "windows":
// 1. winget
if _, err := exec.LookPath("winget"); err == nil {
g.Log().Infof(ctx, "[python3] 通过 winget 安装...")
cmd := exec.CommandContext(ctx, "winget", "install", "--id", "Python.Python.3", "-e", "--accept-package-agreements")
output, err := cmd.CombinedOutput()
if err == nil {
g.Log().Info(ctx, "[python3] ✔ 安装成功")
return
}
g.Log().Warningf(ctx, "[python3] ⚠ winget 安装失败: %v\n%s", err, string(output))
}
// 2. choco
if _, err := exec.LookPath("choco"); err == nil {
g.Log().Infof(ctx, "[python3] 通过 choco 安装...")
cmd := exec.CommandContext(ctx, "choco", "install", "python", "-y")
output, err := cmd.CombinedOutput()
if err == nil {
g.Log().Info(ctx, "[python3] ✔ 安装成功")
return
}
g.Log().Warningf(ctx, "[python3] ⚠ choco 安装失败: %v\n%s", err, string(output))
}
g.Log().Warningf(ctx, `[python3] ⚠ 请手动安装 Python3:
1. winget install --id Python.Python.3 -e
2. 从 https://www.python.org/downloads/ 下载安装(记得勾选"Add to PATH"`)
default:
g.Log().Warningf(ctx, "[python3] ⚠ 不支持的平台(%s),请手动安装 Python3", runtime.GOOS)
}
}