cli/internal/ffmpeg/ffprobe.go

142 lines
4.0 KiB
Go

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
}