fix(playback): probe real VideoToolbox modes, fix chapterthumbs extraction
Address adversarial review: the VideoToolbox probe now smoke-encodes both encoders in the exact constant-quality (-q:v) mode the uncapped transcode path emits, so Intel Macs without qscale support resolve auto to software instead of approving unrunnable commands; and chapter-thumbnail extraction gains a videotoolbox case (hardware decode, software filters) instead of misclassifying CPU args as a hardware attempt — HDR thumbnails on macOS now tone-map via the shared CPU chain. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
f701702a55
commit
2489c3160b
@@ -202,9 +202,12 @@ func ffmpegSupportsVideoToolbox(ffmpegPath string) (bool, string) {
|
||||
}
|
||||
|
||||
// probeFFmpegVideoToolbox verifies the configured FFmpeg exposes VideoToolbox
|
||||
// decode plus both encoders, then smoke-encodes one frame. No filter probes:
|
||||
// the videotoolbox pipeline keeps decoded frames in system memory, so the
|
||||
// regular software filter graph applies (see appendHWAccelArgs).
|
||||
// decode plus both encoders, then smoke-encodes one frame per encoder in the
|
||||
// same constant-quality mode the uncapped transcode path emits (-q:v needs an
|
||||
// Apple Silicon compression session; Intel Macs must fail here so auto
|
||||
// resolves to software instead of approving commands that cannot run). No
|
||||
// filter probes: the videotoolbox pipeline keeps decoded frames in system
|
||||
// memory, so the regular software filter graph applies (see appendHWAccelArgs).
|
||||
func probeFFmpegVideoToolbox(ffmpegPath string) nvencProbeResult {
|
||||
if output, err := runFFmpegProbe(ffmpegPath, "-hide_banner", "-hwaccels"); err != nil {
|
||||
return nvencProbeResult{reason: "hwaccels probe failed: " + probeFailure(err, output)}
|
||||
@@ -220,18 +223,27 @@ func probeFFmpegVideoToolbox(ffmpegPath string) nvencProbeResult {
|
||||
return nvencProbeResult{reason: "hevc_videotoolbox encoder unavailable"}
|
||||
}
|
||||
|
||||
if output, err := runFFmpegProbe(ffmpegPath,
|
||||
"-hide_banner",
|
||||
"-loglevel", "error",
|
||||
"-f", "lavfi",
|
||||
"-i", "testsrc2=size=640x360:rate=1",
|
||||
"-frames:v", "1",
|
||||
"-an",
|
||||
"-c:v", "h264_videotoolbox",
|
||||
"-f", "null",
|
||||
"-",
|
||||
); err != nil {
|
||||
return nvencProbeResult{reason: "h264_videotoolbox smoke encode failed: " + probeFailure(err, output)}
|
||||
for _, smoke := range []struct {
|
||||
encoder string
|
||||
quality string
|
||||
}{
|
||||
{"h264_videotoolbox", "65"},
|
||||
{"hevc_videotoolbox", "60"},
|
||||
} {
|
||||
if output, err := runFFmpegProbe(ffmpegPath,
|
||||
"-hide_banner",
|
||||
"-loglevel", "error",
|
||||
"-f", "lavfi",
|
||||
"-i", "testsrc2=size=640x360:rate=1",
|
||||
"-frames:v", "1",
|
||||
"-an",
|
||||
"-c:v", smoke.encoder,
|
||||
"-q:v", smoke.quality,
|
||||
"-f", "null",
|
||||
"-",
|
||||
); err != nil {
|
||||
return nvencProbeResult{reason: smoke.encoder + " smoke encode failed: " + probeFailure(err, output)}
|
||||
}
|
||||
}
|
||||
|
||||
return nvencProbeResult{available: true}
|
||||
|
||||
@@ -376,6 +376,38 @@ func TestResolveHWAccelWithFFmpegDarwinRequiresBothVTEncoders(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveHWAccelWithFFmpegDarwinFallsBackToNoneWhenSmokeEncodeFails(t *testing.T) {
|
||||
setupHWAccelTest(t)
|
||||
currentGOOS = "darwin"
|
||||
ffmpeg := writeFakeFFmpeg(t, fakeFFmpegProbe{videotoolbox: true, h264VT: true, hevcVT: true})
|
||||
|
||||
if got := ResolveHWAccelWithFFmpeg("auto", ffmpeg.path); got != "none" {
|
||||
t.Fatalf("ResolveHWAccelWithFFmpeg() = %q, want none when smoke encode fails", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVideoToolboxProbeSmokesBothEncodersInConstantQualityMode(t *testing.T) {
|
||||
setupHWAccelTest(t)
|
||||
currentGOOS = "darwin"
|
||||
ffmpeg := writeFakeFFmpeg(t, successfulVideoToolboxProbe())
|
||||
|
||||
if got := ResolveHWAccelWithFFmpeg("auto", ffmpeg.path); got != "videotoolbox" {
|
||||
t.Fatalf("ResolveHWAccelWithFFmpeg() = %q, want videotoolbox", got)
|
||||
}
|
||||
log, err := os.ReadFile(ffmpeg.logPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read probe log: %v", err)
|
||||
}
|
||||
// The smoke encodes must exercise the exact uncapped (-q:v) mode the
|
||||
// transcode arg builder emits — Intel Macs fail that mode at session
|
||||
// creation, so probing without it would approve unrunnable commands.
|
||||
for _, want := range []string{"-c:v h264_videotoolbox -q:v 65", "-c:v hevc_videotoolbox -q:v 60"} {
|
||||
if !strings.Contains(string(log), want) {
|
||||
t.Fatalf("probe log missing %q:\n%s", want, log)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExplicitVideoToolboxBypassesFFmpegProbe(t *testing.T) {
|
||||
setupHWAccelTest(t)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user