Git Repository
Mini Video Library
Mini Video Library scannt lokale Medienordner, verwaltet Metadaten und Vorschaubilder und streamt kompatible Videos direkt per HTTP an den Browser.
HTTPS
https://zanvex.de/git/mini-video-library.gitinternal/metadata/probe.go
Zum Verzeichnispackage metadata
import (
"context"
"encoding/json"
"fmt"
"os/exec"
"strconv"
"strings"
)
type Audio struct {
Index int `json:"index"`
Language, Codec string
Channels int `json:"channels"`
Default bool `json:"default"`
}
type Subtitle struct {
Index int `json:"index"`
Language, Codec string
Forced, Default bool
}
type Chapter struct {
Start, End float64
Title string
}
type Result struct {
Duration float64
VideoCodec string
Width, Height int
FPS float64
Bitrate int64
Container string
HDR bool
Audio []Audio
Subtitles []Subtitle
Chapters []Chapter
}
type raw struct {
Format struct {
Name string `json:"format_name"`
Duration string `json:"duration"`
BitRate string `json:"bit_rate"`
} `json:"format"`
Streams []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"`
AvgFrameRate string `json:"avg_frame_rate"`
ColorTransfer string `json:"color_transfer"`
Tags map[string]string `json:"tags"`
Disposition struct{ Default, Forced int } `json:"disposition"`
} `json:"streams"`
Chapters []struct {
StartTime, EndTime string
Tags map[string]string
} `json:"chapters"`
}
func Probe(ctx context.Context, binary, path string) (Result, error) {
cmd := exec.CommandContext(ctx, binary, "-v", "error", "-show_format", "-show_streams", "-show_chapters", "-of", "json", path)
b, e := cmd.CombinedOutput()
if e != nil {
message := strings.TrimSpace(string(b))
if message == "" {
message = "no diagnostic output"
}
return Result{}, fmt.Errorf("ffprobe %s: %w: %s", path, e, message)
}
var x raw
if e = json.Unmarshal(b, &x); e != nil {
return Result{}, e
}
r := Result{Container: strings.Split(x.Format.Name, ",")[0]}
r.Duration, _ = strconv.ParseFloat(x.Format.Duration, 64)
r.Bitrate, _ = strconv.ParseInt(x.Format.BitRate, 10, 64)
for _, s := range x.Streams {
lang := s.Tags["language"]
switch s.CodecType {
case "video":
if r.VideoCodec == "" {
r.VideoCodec = s.CodecName
r.Width = s.Width
r.Height = s.Height
r.FPS = parseRate(s.AvgFrameRate)
r.HDR = strings.Contains(strings.ToLower(s.ColorTransfer), "2084") || strings.Contains(strings.ToLower(s.ColorTransfer), "hlg")
}
case "audio":
r.Audio = append(r.Audio, Audio{Index: s.Index, Language: lang, Codec: s.CodecName, Channels: s.Channels, Default: s.Disposition.Default == 1})
case "subtitle":
r.Subtitles = append(r.Subtitles, Subtitle{Index: s.Index, Language: lang, Codec: s.CodecName, Forced: s.Disposition.Forced == 1, Default: s.Disposition.Default == 1})
}
}
for _, c := range x.Chapters {
a, _ := strconv.ParseFloat(c.StartTime, 64)
b, _ := strconv.ParseFloat(c.EndTime, 64)
r.Chapters = append(r.Chapters, Chapter{a, b, c.Tags["title"]})
}
return r, nil
}
func parseRate(s string) float64 {
p := strings.Split(s, "/")
if len(p) != 2 {
return 0
}
a, _ := strconv.ParseFloat(p[0], 64)
b, _ := strconv.ParseFloat(p[1], 64)
if b == 0 {
return 0
}
return a / b
}