unarr/internal/library/mediainfo/ffmpeg.go
Deivid Soto 727ab19468 feat(mediainfo): ResolveFFmpeg + DownloadFFmpeg mirroring ffprobe pattern
Adds the ffmpeg-binary half of the resolution stack so the upcoming
WebRTC streaming transcoder (Fase 3.3) has a single point of entry.

Search order matches ResolveFFprobe so operators don't need to learn a
second mental model:
  1. Explicit path  (--ffmpeg flag / library.ffmpeg_path config)
  2. FFMPEG_PATH env var
  3. "ffmpeg" on PATH (system install)
  4. Adjacent to the unarr executable (release tarball bundles it here —
     this is the preferred path; see Fase 3.2 goreleaser changes)
  5. Cache dir (sibling of the cached ffprobe binary)
  6. Auto-download from ffbinaries.com (~70MB) as last resort

Includes:
- internal/library/mediainfo/ffmpeg.go         — ResolveFFmpeg + actionable
  Docker / non-Docker error messages
- internal/library/mediainfo/ffmpeg_download.go — DownloadFFmpeg, reuses
  ffprobePlatformKey + ffprobeAPIClient + ffprobeDLClient + extractFromZip
  helpers; bumps maxZipSize to 200MB (ffmpeg static is ~70-100MB)
- internal/config: LibraryConfig.FFmpegPath toml field for explicit paths
- 4 unit tests: explicit OK, explicit missing, env var, sibling cache path

Tarball bundling and the actual transcoding pipeline land in the next
two commits.
2026-05-06 09:49:32 +02:00

79 lines
2.4 KiB
Go

package mediainfo
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
)
// ResolveFFmpeg finds the ffmpeg binary. Search order mirrors ResolveFFprobe
// so the same operator setup works for both:
// 1. Explicit path (--ffmpeg flag / library.ffmpeg_path config)
// 2. FFMPEG_PATH env var
// 3. "ffmpeg" on PATH
// 4. Adjacent to the current executable (release tarball bundles ffmpeg
// next to the unarr binary — this is the preferred install path)
// 5. Previously downloaded in the unarr cache dir
// 6. Auto-download static binary as last resort (~50MB, slow start)
//
// ffmpeg is required for the WebRTC streaming pipeline; ffprobe alone can't
// transcode HEVC/MKV to browser-friendly H.264/MP4 fragments.
func ResolveFFmpeg(explicit string) (string, error) {
if explicit != "" {
if _, err := os.Stat(explicit); err == nil {
return explicit, nil
}
return "", fmt.Errorf("ffmpeg not found at explicit path: %s", explicit)
}
if envPath := os.Getenv("FFMPEG_PATH"); envPath != "" {
if _, err := os.Stat(envPath); err == nil {
return envPath, nil
}
}
if p, err := exec.LookPath("ffmpeg"); err == nil {
return p, nil
}
if exePath, err := os.Executable(); err == nil {
name := "ffmpeg"
if runtime.GOOS == "windows" {
name = "ffmpeg.exe"
}
adjacent := filepath.Join(filepath.Dir(exePath), name)
if _, err := os.Stat(adjacent); err == nil {
return adjacent, nil
}
}
if cached, err := FFmpegCachePath(); err == nil {
if _, err := os.Stat(cached); err == nil {
return cached, nil
}
}
if p, err := DownloadFFmpeg(); err == nil {
return p, nil
}
if isDocker() {
return "", fmt.Errorf(
"ffmpeg not found and auto-download failed (read-only filesystem?).\n" +
"Options:\n" +
" • Use the official image: torrentclaw/unarr (includes ffmpeg)\n" +
" • Set FFMPEG_PATH env var to point to a pre-installed ffmpeg binary\n" +
" • Add to config.toml: [library]\\nffmpeg_path = \"/path/to/ffmpeg\"",
)
}
return "", fmt.Errorf(
"ffmpeg not found and auto-download failed.\n" +
"Options:\n" +
" • Install ffmpeg: sudo apt install ffmpeg (or brew install ffmpeg)\n" +
" • Use the unarr release tarball — ffmpeg is bundled next to the binary\n" +
" • Set FFMPEG_PATH env var to point to the ffmpeg binary\n" +
" • Add to config.toml: [library]\\nffmpeg_path = \"/path/to/ffmpeg\"",
)
}