Initial commit: sheepflix media streaming API
Go API for browsing/searching media files with ffprobe metadata, token auth, and go-transcode integration for HLS streaming. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
6625e5927e
11 changed files with 603 additions and 0 deletions
109
internal/probe/probe.go
Normal file
109
internal/probe/probe.go
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue