unarr/internal/library/mediainfo/ffmpeg_test.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

78 lines
2.2 KiB
Go

package mediainfo
import (
"os"
"path/filepath"
"runtime"
"testing"
)
// TestResolveFFmpeg_ExplicitOK verifies the explicit-path branch returns
// the requested binary if it exists on disk.
func TestResolveFFmpeg_ExplicitOK(t *testing.T) {
dir := t.TempDir()
fake := filepath.Join(dir, "ffmpeg")
if err := os.WriteFile(fake, []byte("#!/bin/sh\n"), 0o755); err != nil {
t.Fatalf("write fake: %v", err)
}
got, err := ResolveFFmpeg(fake)
if err != nil {
t.Fatalf("ResolveFFmpeg(explicit): %v", err)
}
if got != fake {
t.Fatalf("got %q want %q", got, fake)
}
}
// TestResolveFFmpeg_ExplicitMissing returns a clear error when the path
// the operator supplied doesn't exist — we do NOT silently fall back.
func TestResolveFFmpeg_ExplicitMissing(t *testing.T) {
_, err := ResolveFFmpeg("/nonexistent/path/ffmpeg-XXXXXX")
if err == nil {
t.Fatal("expected error for missing explicit path")
}
}
// TestResolveFFmpeg_EnvVar honours FFMPEG_PATH when no explicit path is given.
func TestResolveFFmpeg_EnvVar(t *testing.T) {
dir := t.TempDir()
fake := filepath.Join(dir, "ffmpeg")
if err := os.WriteFile(fake, []byte("#!/bin/sh\n"), 0o755); err != nil {
t.Fatalf("write fake: %v", err)
}
t.Setenv("FFMPEG_PATH", fake)
// Hide the real ffmpeg from PATH so the env var is the next branch hit.
t.Setenv("PATH", "/nonexistent")
got, err := ResolveFFmpeg("")
if err != nil {
t.Fatalf("ResolveFFmpeg(env): %v", err)
}
if got != fake {
t.Fatalf("got %q want %q (env-var branch)", got, fake)
}
}
// TestFFmpegCachePath returns a sibling path to the ffprobe cache,
// consistent with the install layout the tarball produces.
func TestFFmpegCachePath(t *testing.T) {
got, err := FFmpegCachePath()
if err != nil {
t.Fatalf("FFmpegCachePath: %v", err)
}
want := "ffmpeg"
if runtime.GOOS == "windows" {
want = "ffmpeg.exe"
}
if filepath.Base(got) != want {
t.Fatalf("cache path basename = %q want %q", filepath.Base(got), want)
}
probeCache, err := FFprobeCachePath()
if err != nil {
t.Fatalf("FFprobeCachePath: %v", err)
}
if filepath.Dir(got) != filepath.Dir(probeCache) {
t.Fatalf("ffmpeg cache (%s) and ffprobe cache (%s) should share a directory", got, probeCache)
}
}