diff --git a/edge-ai-platform/installer/app.go b/edge-ai-platform/installer/app.go index 00dd023..3a68553 100644 --- a/edge-ai-platform/installer/app.go +++ b/edge-ai-platform/installer/app.go @@ -231,7 +231,8 @@ func (inst *Installer) runInstall(config InstallConfig) { {"Extracting scripts", 48, true, inst.stepExtractScripts}, {"Configuring system", 55, false, inst.stepConfigureSystem}, {"Setting up USB driver", 62, false, inst.stepSetupLibusb}, - {"Setting up Python environment", 72, false, inst.stepSetupPython}, + {"Setting up Python environment", 70, false, inst.stepSetupPython}, + {"Installing media tools", 78, false, inst.stepInstallFfmpeg}, {"Writing configuration", 85, true, inst.stepWriteConfig}, {"Verifying installation", 90, false, inst.stepVerify}, {"Setting up auto-start launcher", 95, false, inst.stepAutoRestart}, @@ -519,6 +520,24 @@ func (inst *Installer) setupPythonVenv(installDir string) error { return nil } +func (inst *Installer) stepInstallFfmpeg(config InstallConfig) error { + needFfmpeg := true + if _, err := exec.LookPath("ffmpeg"); err == nil { + needFfmpeg = false + } + + needYtdlp := true + if _, err := exec.LookPath("yt-dlp"); err == nil { + needYtdlp = false + } + + if !needFfmpeg && !needYtdlp { + return nil // both already installed + } + + return installFfmpeg(inst, needFfmpeg, needYtdlp) +} + func (inst *Installer) stepWriteConfig(config InstallConfig) error { cfgDir := platformConfigDir() if err := os.MkdirAll(cfgDir, 0755); err != nil { diff --git a/edge-ai-platform/installer/platform_darwin.go b/edge-ai-platform/installer/platform_darwin.go index 304c3c2..8f46fcd 100644 --- a/edge-ai-platform/installer/platform_darwin.go +++ b/edge-ai-platform/installer/platform_darwin.go @@ -138,6 +138,39 @@ func installAutoRestart(installDir string) error { return nil } +func installFfmpeg(inst *Installer, needFfmpeg, needYtdlp bool) error { + if _, err := exec.LookPath("brew"); err != nil { + return fmt.Errorf("Homebrew not found — install from https://brew.sh then run: brew install ffmpeg") + } + + if needFfmpeg { + inst.emitProgress(ProgressEvent{ + Step: "ffmpeg", + Message: "Installing ffmpeg via Homebrew (this may take a few minutes)...", + Percent: 79, + }) + cmd := exec.Command("brew", "install", "ffmpeg") + if out, err := cmd.CombinedOutput(); err != nil { + return fmt.Errorf("brew install ffmpeg failed: %s — %w", string(out), err) + } + } + + if needYtdlp { + inst.emitProgress(ProgressEvent{ + Step: "ffmpeg", + Message: "Installing yt-dlp via Homebrew...", + Percent: 80, + }) + cmd := exec.Command("brew", "install", "yt-dlp") + if out, err := cmd.CombinedOutput(); err != nil { + // Non-fatal: yt-dlp is optional + _ = out + } + } + + return nil +} + func removeAutoRestart() { home, err := os.UserHomeDir() if err != nil { diff --git a/edge-ai-platform/installer/platform_linux.go b/edge-ai-platform/installer/platform_linux.go index 15ad61b..efa1840 100644 --- a/edge-ai-platform/installer/platform_linux.go +++ b/edge-ai-platform/installer/platform_linux.go @@ -109,6 +109,38 @@ func installAutoRestart(installDir string) error { return nil } +func installFfmpeg(inst *Installer, needFfmpeg, needYtdlp bool) error { + if _, err := exec.LookPath("apt-get"); err != nil { + return fmt.Errorf("package manager not found — please install ffmpeg manually") + } + + if needFfmpeg { + inst.emitProgress(ProgressEvent{ + Step: "ffmpeg", + Message: "Installing ffmpeg via apt-get...", + Percent: 79, + }) + cmd := exec.Command("pkexec", "apt-get", "install", "-y", "ffmpeg") + if out, err := cmd.CombinedOutput(); err != nil { + return fmt.Errorf("apt install ffmpeg failed: %s — %w", string(out), err) + } + } + + if needYtdlp { + inst.emitProgress(ProgressEvent{ + Step: "ffmpeg", + Message: "Installing yt-dlp via pip...", + Percent: 80, + }) + cmd := exec.Command("pip3", "install", "yt-dlp") + if out, err := cmd.CombinedOutput(); err != nil { + _ = out // Non-fatal + } + } + + return nil +} + func removeAutoRestart() { exec.Command("systemctl", "--user", "disable", "--now", systemdServiceName+".service").Run() diff --git a/edge-ai-platform/installer/platform_windows.go b/edge-ai-platform/installer/platform_windows.go index 5112783..e59194b 100644 --- a/edge-ai-platform/installer/platform_windows.go +++ b/edge-ai-platform/installer/platform_windows.go @@ -155,6 +155,53 @@ func installAutoRestart(installDir string) error { return nil } +func installFfmpeg(inst *Installer, needFfmpeg, needYtdlp bool) error { + if _, err := exec.LookPath("winget"); err != nil { + return fmt.Errorf("winget not found — please install ffmpeg manually from https://ffmpeg.org") + } + + if needFfmpeg { + inst.emitProgress(ProgressEvent{ + Step: "ffmpeg", + Message: "Installing ffmpeg via winget...", + Percent: 79, + }) + cmd := exec.Command("winget", "install", "Gyan.FFmpeg", + "--accept-source-agreements", "--accept-package-agreements", "--silent") + if out, err := cmd.CombinedOutput(); err != nil { + return fmt.Errorf("winget install ffmpeg failed: %s — %w", string(out), err) + } + + // winget installs ffmpeg to a PATH that requires shell restart. + // Add common install location to current process PATH so subsequent checks work. + for _, dir := range []string{ + filepath.Join(os.Getenv("LOCALAPPDATA"), "Microsoft", "WinGet", "Links"), + `C:\ffmpeg\bin`, + filepath.Join(os.Getenv("ProgramFiles"), "FFmpeg", "bin"), + } { + if _, err := os.Stat(dir); err == nil { + os.Setenv("PATH", dir+";"+os.Getenv("PATH")) + } + } + } + + if needYtdlp { + inst.emitProgress(ProgressEvent{ + Step: "ffmpeg", + Message: "Installing yt-dlp via winget...", + Percent: 80, + }) + cmd := exec.Command("winget", "install", "yt-dlp.yt-dlp", + "--accept-source-agreements", "--accept-package-agreements", "--silent") + if out, err := cmd.CombinedOutput(); err != nil { + // Non-fatal: yt-dlp is optional + _ = out + } + } + + return nil +} + func removeAutoRestart() { // Remove Registry Run key exec.Command("reg", "delete",