package probe import ( "encoding/json" "fmt" "os/exec" "strconv" ) type ProbeResult struct { Duration float64 `json:"duration"` Width int `json:"width"` Height int `json:"height"` VideoCodec string `json:"video_codec"` Bitrate int64 `json:"bitrate"` Audio []AudioTrack `json:"audio_tracks"` Subtitles []SubTrack `json:"subtitle_tracks"` } type AudioTrack struct { Index int `json:"index"` Codec string `json:"codec"` Language string `json:"language"` Channels int `json:"channels"` } type SubTrack struct { Index int `json:"index"` Codec string `json:"codec"` Language string `json:"language"` URL string `json:"url,omitempty"` } type ffprobeOutput struct { Format ffprobeFormat `json:"format"` Streams []ffprobeStream `json:"streams"` } type ffprobeFormat struct { Duration string `json:"duration"` BitRate string `json:"bit_rate"` } type ffprobeStream struct { Index int `json:"index"` CodecType string `json:"codec_type"` CodecName string `json:"codec_name"` Width int `json:"width"` Height int `json:"height"` Channels int `json:"channels"` Tags struct { Language string `json:"language"` } `json:"tags"` } // Run executes ffprobe on the given file and returns parsed metadata. func Run(path string) (*ProbeResult, error) { cmd := exec.Command("ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", path, ) out, err := cmd.Output() if err != nil { return nil, fmt.Errorf("ffprobe failed: %w", err) } var raw ffprobeOutput if err := json.Unmarshal(out, &raw); err != nil { return nil, fmt.Errorf("ffprobe parse failed: %w", err) } result := &ProbeResult{} if d, err := strconv.ParseFloat(raw.Format.Duration, 64); err == nil { result.Duration = d } if br, err := strconv.ParseInt(raw.Format.BitRate, 10, 64); err == nil { result.Bitrate = br } for _, s := range raw.Streams { switch s.CodecType { case "video": if result.VideoCodec == "" { result.VideoCodec = s.CodecName result.Width = s.Width result.Height = s.Height } case "audio": result.Audio = append(result.Audio, AudioTrack{ Index: s.Index, Codec: s.CodecName, Language: s.Tags.Language, Channels: s.Channels, }) case "subtitle": result.Subtitles = append(result.Subtitles, SubTrack{ Index: s.Index, Codec: s.CodecName, Language: s.Tags.Language, }) } } return result, nil }