fix(v2): log redacted ffmpeg/ffprobe stderr on stream failure
The stream worker discarded ffmpeg stderr, so reconnect loops were opaque. Capture stderr and log it with the RTSP URL/credentials redacted, so the real cause (auth, timeout, option, URL parse) is diagnosable without leaking the source address. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2,18 +2,32 @@
|
|||||||
package source
|
package source
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var rtspURLPattern = regexp.MustCompile(`rtsp://[^\s'"]+`)
|
||||||
|
|
||||||
|
// redact removes the resolved source URL (which may carry credentials) from any
|
||||||
|
// FFmpeg/FFprobe diagnostic text before it is logged.
|
||||||
|
func redact(text, sourceURL string) string {
|
||||||
|
if sourceURL != "" {
|
||||||
|
text = strings.ReplaceAll(text, sourceURL, "<source>")
|
||||||
|
}
|
||||||
|
return rtspURLPattern.ReplaceAllString(text, "rtsp://<redacted>")
|
||||||
|
}
|
||||||
|
|
||||||
type Status string
|
type Status string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -195,8 +209,12 @@ func probe(ctx context.Context, config Config) (Metadata, error) {
|
|||||||
args := []string{"-v", "error", "-select_streams", "v:0", "-show_entries", "stream=width,height,r_frame_rate", "-of", "json"}
|
args := []string{"-v", "error", "-select_streams", "v:0", "-show_entries", "stream=width,height,r_frame_rate", "-of", "json"}
|
||||||
args = appendInputOptions(args, config)
|
args = appendInputOptions(args, config)
|
||||||
args = append(args, "-i", config.SourceURL)
|
args = append(args, "-i", config.SourceURL)
|
||||||
output, err := exec.CommandContext(ctx, config.FFprobePath, args...).Output()
|
command := exec.CommandContext(ctx, config.FFprobePath, args...)
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
command.Stderr = &stderr
|
||||||
|
output, err := command.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Printf("ffprobe 探测失败:%s(%v)", strings.TrimSpace(redact(stderr.String(), config.SourceURL)), err)
|
||||||
return Metadata{}, errors.New("unable to probe video metadata")
|
return Metadata{}, errors.New("unable to probe video metadata")
|
||||||
}
|
}
|
||||||
var parsed struct {
|
var parsed struct {
|
||||||
@@ -230,7 +248,8 @@ func decode(ctx context.Context, config Config, metadata Metadata, publish func(
|
|||||||
args := append([]string{"-hide_banner", "-loglevel", "error"}, appendInputOptions(nil, config)...)
|
args := append([]string{"-hide_banner", "-loglevel", "error"}, appendInputOptions(nil, config)...)
|
||||||
args = append(args, "-i", config.SourceURL, "-an", "-sn", "-dn", "-f", "rawvideo", "-pix_fmt", "bgr24", "-")
|
args = append(args, "-i", config.SourceURL, "-an", "-sn", "-dn", "-f", "rawvideo", "-pix_fmt", "bgr24", "-")
|
||||||
command := exec.CommandContext(ctx, config.FFmpegPath, args...)
|
command := exec.CommandContext(ctx, config.FFmpegPath, args...)
|
||||||
command.Stderr = io.Discard
|
var stderr bytes.Buffer
|
||||||
|
command.Stderr = &stderr
|
||||||
stdout, err := command.StdoutPipe()
|
stdout, err := command.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("unable to read video frames")
|
return errors.New("unable to read video frames")
|
||||||
@@ -247,6 +266,7 @@ func decode(ctx context.Context, config Config, metadata Metadata, publish func(
|
|||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
|
log.Printf("ffmpeg 解码停止:%s(%v)", strings.TrimSpace(redact(stderr.String(), config.SourceURL)), err)
|
||||||
return errors.New("video decoder stopped")
|
return errors.New("video decoder stopped")
|
||||||
}
|
}
|
||||||
publish(frame)
|
publish(frame)
|
||||||
|
|||||||
Reference in New Issue
Block a user