package ffmpeg import ( "bytes" "context" "encoding/json" "io" "os/exec" "strings" "github.com/samber/lo" ) type Result struct { Format *Format `json:"format"` Streams []*Stream `json:"streams"` } func (r *Result) HasAttachedPic() bool { return lo.ContainsBy(r.Streams, func(s *Stream) bool { return s.CodecType == "video" }) } func (r *Result) getTagByKey(key string) string { for k, v := range r.Format.Tags { if key == strings.ToLower(k) { return v } } for _, stream := range r.Streams { // try to find in streams if stream.CodecType != "audio" { continue } for k, v := range stream.Tags { if key == strings.ToLower(k) { return v } } } return "" } func (r *Result) GetTitle() string { return r.getTagByKey("title") } func (r *Result) GetAlbum() string { return r.getTagByKey("album") } func (r *Result) GetArtists() []string { artists := strings.Split(r.getTagByKey("artist"), "/") for i := range artists { artists[i] = strings.TrimSpace(artists[i]) } return artists } func (r *Result) HasMetadata() bool { return r.GetTitle() != "" || r.GetAlbum() != "" || len(r.GetArtists()) > 0 } type Format struct { Filename string `json:"filename"` NbStreams int `json:"nb_streams"` NbPrograms int `json:"nb_programs"` FormatName string `json:"format_name"` FormatLongName string `json:"format_long_name"` StartTime string `json:"start_time"` Duration string `json:"duration"` BitRate string `json:"bit_rate"` ProbeScore int `json:"probe_score"` Tags map[string]string `json:"tags"` } type Stream struct { Index int `json:"index"` CodecName string `json:"codec_name"` CodecLongName string `json:"codec_long_name"` CodecType string `json:"codec_type"` CodecTagString string `json:"codec_tag_string"` CodecTag string `json:"codec_tag"` SampleFmt string `json:"sample_fmt"` SampleRate string `json:"sample_rate"` Channels int `json:"channels"` ChannelLayout string `json:"channel_layout"` BitsPerSample int `json:"bits_per_sample"` RFrameRate string `json:"r_frame_rate"` AvgFrameRate string `json:"avg_frame_rate"` TimeBase string `json:"time_base"` StartPts int `json:"start_pts"` StartTime string `json:"start_time"` BitRate string `json:"bit_rate"` Disposition *ProbeDisposition `json:"disposition"` Tags map[string]string `json:"tags"` } type ProbeDisposition struct { Default int `json:"default"` Dub int `json:"dub"` Original int `json:"original"` Comment int `json:"comment"` Lyrics int `json:"lyrics"` Karaoke int `json:"karaoke"` Forced int `json:"forced"` HearingImpaired int `json:"hearing_impaired"` VisualImpaired int `json:"visual_impaired"` CleanEffects int `json:"clean_effects"` AttachedPic int `json:"attached_pic"` TimedThumbnails int `json:"timed_thumbnails"` Captions int `json:"captions"` Descriptions int `json:"descriptions"` Metadata int `json:"metadata"` Dependent int `json:"dependent"` StillImage int `json:"still_image"` } func ProbeReader(ctx context.Context, rd io.Reader) (*Result, error) { cmd := exec.CommandContext(ctx, "ffprobe", "-v", "quiet", // disable logging "-print_format", "json", // use json format "-show_format", "-show_streams", "-show_error", // retrieve format and streams "pipe:0", // input from stdin ) cmd.Stdin = rd stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{} cmd.Stdout, cmd.Stderr = stdout, stderr if err := cmd.Run(); err != nil { return nil, err } ret := new(Result) if err := json.Unmarshal(stdout.Bytes(), ret); err != nil { return nil, err } return ret, nil }