sheepflix/internal/probe/probe.go

111 lines
2.5 KiB
Go
Raw Permalink Normal View History

package probe
import (
"encoding/json"
"fmt"
"os/exec"
"strconv"
)
type ProbeResult struct {
2026-08-26 02:16:50 -04:00
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"`
2026-08-26 02:16:50 -04:00
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
}