package metadata import ( "context" "encoding/json" "fmt" "os/exec" ) type ffprobeOutput struct { Format ffprobeFormat `json:"format"` Streams []ffprobeStream `json:"streams"` } type ffprobeFormat struct { FormatName string `json:"format_name"` Duration string `json:"duration"` BitRate string `json:"bit_rate"` Tags map[string]string `json:"tags"` } type ffprobeStream struct { Index int `json:"index"` CodecType string `json:"codec_type"` // "audio", "video", "subtitle", ... CodecName string `json:"codec_name"` SampleRate string `json:"sample_rate"` Channels int `json:"channels"` Duration string `json:"duration"` BitRate string `json:"bit_rate"` Disposition map[string]int `json:"disposition"` Tags map[string]string `json:"tags"` } // Prober runs ffprobe to extract tags and technical properties from a // media file. type Prober struct { FFprobePath string } // Probe reads metadata from the file at path. path must already be a // validated, absolute filesystem path within a configured music // folder — Probe does not perform any authorization or traversal checks // itself. func (p *Prober) Probe(ctx context.Context, path string) (*Tags, error) { ffprobePath := p.FFprobePath if ffprobePath == "" { ffprobePath = "ffprobe" } cmd := exec.CommandContext(ctx, ffprobePath, "-v", "error", "-print_format", "json", "-show_format", "-show_streams", "--", path, ) out, err := cmd.Output() if err != nil { if ee, ok := err.(*exec.ExitError); ok { return nil, fmt.Errorf("ffprobe: %w: %s", err, string(ee.Stderr)) } return nil, fmt.Errorf("ffprobe: %w", err) } var raw ffprobeOutput if err := json.Unmarshal(out, &raw); err != nil { return nil, fmt.Errorf("parsing ffprobe output: %w", err) } return parseFFprobeOutput(raw), nil }