110 lines
2.5 KiB
Go
110 lines
2.5 KiB
Go
|
|
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"`
|
||
|
|
}
|
||
|
|
|
||
|
|
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
|
||
|
|
}
|