From eb109f70ac371a72cff6f48db17c76add120bdef Mon Sep 17 00:00:00 2001 From: Deivid Soto Date: Wed, 27 May 2026 12:48:40 +0200 Subject: [PATCH] feat(agent): send full transcoder diagnostic in register payload (0.9.12) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Daemon now runs engine.DetectHWAccelDiagnostic at startup (instead of the lighter DetectHWAccel) and ships the full picture — ffmpeg version, resolved binary path, HW encoders compiled in, device files / drivers detected — up to the server in the RegisterRequest payload. Why: the most common cause of slow first-play is a software-only ffmpeg build. Surfacing the diagnostic in the web AgentsTab "Diagnose transcoder" modal lets a user see *why* their backend landed on libx264 (e.g. brew's default formula ships without --enable-nvenc, or the container is missing /dev/nvidia0) without SSHing in to run `unarr probe-hwaccel` manually. Also emits a single `[transcode]` startup log line summarising the same data — convenient for `journalctl --user -u unarr | grep transcode`. Bounded by a 10 s context so a hung ffmpeg binary can't stall daemon startup forever. --- CHANGELOG.md | 19 +++++++++++++++++++ internal/agent/daemon.go | 14 +++++++++++++- internal/agent/types.go | 9 +++++++++ internal/cmd/daemon.go | 18 +++++++++++++++++- internal/cmd/version.go | 2 +- 5 files changed, 59 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 534bd99..3d75ac7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.9.12] - 2026-05-27 + +### Added + +- **transcoder diagnostic in register payload**: daemon now sends the full + HWAccel diagnostic (ffmpeg version, resolved binary path, list of HW + encoders compiled in, list of device files / drivers present) up to the + server on register. The web "Diagnose transcoder" modal surfaces these + so a user stuck on software libx264 can see *why* (e.g. ffmpeg shipped + without `--enable-nvenc`, or `/dev/nvidia0` missing inside a container) + without SSHing into their machine + running `unarr probe-hwaccel`. +- **`[transcode]` startup log line**: daemon prints a single one-line + summary of the picked backend + version + binary path + devices at + start. Same data the web shows; convenient for `journalctl --user -u + unarr | grep transcode`. + ## [0.9.11] - 2026-05-27 @@ -486,6 +502,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - add -s -w -trimpath to Makefile, add build-small target with UPX [0.9.11]: https://github.com/torrentclaw/unarr/compare/v0.9.8...v0.9.11 [0.9.8]: https://github.com/torrentclaw/unarr/compare/v0.9.7...v0.9.8 +[0.9.12]: https://github.com/torrentclaw/unarr/compare/v0.9.11...v0.9.12 +[0.9.11]: https://github.com/torrentclaw/unarr/compare/v0.9.8...v0.9.11 +[0.9.8]: https://github.com/torrentclaw/unarr/compare/v0.9.7...v0.9.8 [0.9.7]: https://github.com/torrentclaw/unarr/compare/v0.9.6...v0.9.7 [0.9.6]: https://github.com/torrentclaw/unarr/compare/v0.9.5...v0.9.6 [0.9.5]: https://github.com/torrentclaw/unarr/compare/v0.9.4...v0.9.5 diff --git a/internal/agent/daemon.go b/internal/agent/daemon.go index 68a187f..f7994fb 100644 --- a/internal/agent/daemon.go +++ b/internal/agent/daemon.go @@ -28,7 +28,15 @@ type DaemonConfig struct { ScanPaths []string // configured scan paths for file deletion validation HWAccel string // detected encoder backend ("nvenc"/"qsv"/"vaapi"/"videotoolbox"/"none") MaxTranscodeHeight int // resolution cap the agent can transcode comfortably (px) - AutoUpgrade bool // honor server-flagged upgrades by downloading + restarting (default: true) + // Diagnostic data populated by engine.DetectHWAccelDiagnostic at daemon + // start. Surfaced in the web "Diagnose transcoder" modal — lets a user + // see which encoders the ffmpeg binary supports and which devices the + // host exposes without running `unarr probe-hwaccel`. + FFmpegVersion string // first line of `ffmpeg -version` + FFmpegPath string // resolved binary path + HWEncoders []string // HW-class encoder names found in `ffmpeg -encoders` + HWDevices []string // device files + driver bins detected at probe time + AutoUpgrade bool // honor server-flagged upgrades by downloading + restarting (default: true) } // Daemon manages agent registration and the sync loop. @@ -122,6 +130,10 @@ func (d *Daemon) Register(ctx context.Context) error { TailscaleIP: d.cfg.TailscaleIP, HWAccel: d.cfg.HWAccel, MaxTranscodeHeight: d.cfg.MaxTranscodeHeight, + FFmpegVersion: d.cfg.FFmpegVersion, + FFmpegPath: d.cfg.FFmpegPath, + HWEncoders: d.cfg.HWEncoders, + HWDevices: d.cfg.HWDevices, VPNActive: d.vpnActive, VPNMode: d.vpnMode, VPNServer: d.vpnServer, diff --git a/internal/agent/types.go b/internal/agent/types.go index 00802bc..ae87bb6 100644 --- a/internal/agent/types.go +++ b/internal/agent/types.go @@ -26,6 +26,15 @@ type RegisterRequest struct { // up to 2160p. HWAccel string `json:"hwAccel,omitempty"` MaxTranscodeHeight int `json:"maxTranscodeHeight,omitempty"` + // Diagnostic surface filled by engine.DetectHWAccelDiagnostic at daemon + // start. Surfaced in the web "Diagnose transcoder" modal so users can + // see *why* their HWAccel landed on "none" without running + // `unarr probe-hwaccel` locally — most commonly the ffmpeg binary + // shipped without HW encoders (linuxbrew, brew's default formula). + FFmpegVersion string `json:"ffmpegVersion,omitempty"` + FFmpegPath string `json:"ffmpegPath,omitempty"` + HWEncoders []string `json:"hwEncoders,omitempty"` + HWDevices []string `json:"hwDevices,omitempty"` // Managed-VPN split-tunnel state. The web tracks which agent holds the single // WireGuard slot (1 VPNResellers account = 1 WG keypair = 1 concurrent // connection); other agents are told to use OpenVPN on their host instead. diff --git a/internal/cmd/daemon.go b/internal/cmd/daemon.go index b0cca22..28b948b 100644 --- a/internal/cmd/daemon.go +++ b/internal/cmd/daemon.go @@ -143,7 +143,19 @@ func runDaemonStart() error { // is what the web side uses to decide whether the user should pre-empt // transcoding by downloading a smaller version (4K source on a software // libx264-only host is the canonical case where pre-download wins). - hwAccelPick := engine.DetectHWAccel(context.Background(), cfg.Library.FFmpegPath) + // + // Use the full diagnostic (encoders + devices + ffmpeg version) instead + // of just the picked backend — the extra fields ride along in the + // register payload so the web "Diagnose transcoder" modal can show *why* + // libx264 was selected on a host with a GPU (e.g. brew's ffmpeg without + // --enable-nvenc). 10 s ceiling so a hung ffmpeg binary can't stall + // startup forever. + ffmpegResolved, _ := mediainfo.ResolveFFmpeg(cfg.Library.FFmpegPath) + probeCtx, probeCancel := context.WithTimeout(context.Background(), 10*time.Second) + hwDiag := engine.DetectHWAccelDiagnostic(probeCtx, ffmpegResolved) + probeCancel() + log.Println(hwDiag.LogLine()) + hwAccelPick := hwDiag.Pick maxTranscodeHeight := 1080 if hwAccelPick != engine.HWAccelNone { maxTranscodeHeight = 2160 @@ -162,6 +174,10 @@ func runDaemonStart() error { ScanPaths: library.ResolveScanPaths(cfg.Download.Dir, cfg.Organize.MoviesDir, cfg.Organize.TVShowsDir, cfg.Library.ScanPath), HWAccel: string(hwAccelPick), MaxTranscodeHeight: maxTranscodeHeight, + FFmpegVersion: hwDiag.FFmpegVersion, + FFmpegPath: hwDiag.FFmpegPath, + HWEncoders: hwDiag.Encoders, + HWDevices: hwDiag.Devices, AutoUpgrade: cfg.Daemon.AutoUpgradeEnabled(), } diff --git a/internal/cmd/version.go b/internal/cmd/version.go index 7ed3030..f4f3f21 100644 --- a/internal/cmd/version.go +++ b/internal/cmd/version.go @@ -1,4 +1,4 @@ package cmd // Version is the CLI version. Overridden by goreleaser ldflags at release time. -var Version = "0.9.11" +var Version = "0.9.12"