fix: correct interlace detection and reap idet child

The previous detector substring-matched "TFF"/"BFF" against idet's own
label text, so it returned true on every source, and `cmd.Start()` was
never paired with `Wait()`, leaving a zombie ffmpeg per file.

Run idet bounded with `-frames:v 400 -an -sn -f null -` so it completes,
use CombinedOutput so the child is reaped, and parse the "Multi frame
detection" summary line — interlaced only when TFF+BFF > Progressive.
This commit is contained in:
Esa Kataja
2026-05-16 19:39:33 +03:00
parent 4783d0e0d4
commit 07f999a338
+34 -8
View File
@@ -6,12 +6,15 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"regexp"
"strconv" "strconv"
"strings" "strings"
"videnc-vibe/pkg/types" "videnc-vibe/pkg/types"
) )
var idetSummaryRegex = regexp.MustCompile(`Multi frame detection:\s+TFF:\s*(\d+)\s+BFF:\s*(\d+)\s+Progressive:\s*(\d+)\s+Undetermined:\s*(\d+)`)
type Encoder struct { type Encoder struct {
ffmpegPath string ffmpegPath string
ffprobePath string ffprobePath string
@@ -122,20 +125,43 @@ func (e *Encoder) GetMediaInfo(path string) (width, height int, interlaced bool,
} }
} }
cmd = exec.Command(e.ffmpegPath, "-i", path, "-hide_banner", "-vf", "idet") cmd = exec.Command(e.ffmpegPath,
stderr, _ := cmd.StderrPipe() "-hide_banner",
cmd.Start() "-nostats",
buf := make([]byte, 16384) "-i", path,
n, _ := stderr.Read(buf) "-vf", "idet",
stderr.Close() "-frames:v", "400",
outputStr := string(buf[:n]) "-an", "-sn",
interlaced = strings.Contains(outputStr, "TFF") || strings.Contains(outputStr, "BFF") "-f", "null", "-",
)
idetOut, _ := cmd.CombinedOutput()
interlaced = detectInterlaced(string(idetOut))
fmt.Printf("DEBUG interlaced: %v\n", interlaced) fmt.Printf("DEBUG interlaced: %v\n", interlaced)
return width, height, interlaced, nil return width, height, interlaced, nil
} }
// detectInterlaced parses ffmpeg idet's "Multi frame detection" summary line
// and returns true when TFF+BFF clearly outnumber Progressive frames among
// the decided frames. Undetermined frames are ignored. If the summary line
// is missing, returns false (default to progressive).
func detectInterlaced(idetOutput string) bool {
m := idetSummaryRegex.FindStringSubmatch(idetOutput)
if m == nil {
return false
}
tff, _ := strconv.Atoi(m[1])
bff, _ := strconv.Atoi(m[2])
prog, _ := strconv.Atoi(m[3])
interlaced := tff + bff
decided := interlaced + prog
if decided == 0 {
return false
}
return interlaced > prog
}
func (e *Encoder) calculateZscaleWidth(path string, originalHeight int) (string, int, error) { func (e *Encoder) calculateZscaleWidth(path string, originalHeight int) (string, int, error) {
cmd := exec.Command(e.ffprobePath, "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=width,height,sample_aspect_ratio", "-print_format", "json", path) cmd := exec.Command(e.ffprobePath, "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=width,height,sample_aspect_ratio", "-print_format", "json", path)
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()