Git Repository
miniaudiolib
miniaudiolib scannt lokale Musik- und Hörbuchordner, liest Audio-Metadaten und Cover, verwaltet Favoriten und Playlists und streamt Titel direkt an den Browser.
HTTPS
https://zanvex.de/git/miniaudiolib.gitinternal/musicmeta/probe.go
Zum Verzeichnispackage musicmeta
import (
"context"
"encoding/json"
"fmt"
"os/exec"
"strconv"
"strings"
)
type Result struct {
Title, Artist, AlbumArtist, Album, Genre, Codec, Container string
Year, Track, Disc, SampleRate, Channels int
Duration float64
Bitrate int64
}
type rawProbe struct {
Format struct {
FormatName string `json:"format_name"`
Duration string `json:"duration"`
BitRate string `json:"bit_rate"`
Tags map[string]string `json:"tags"`
} `json:"format"`
Streams []struct {
CodecType string `json:"codec_type"`
CodecName string `json:"codec_name"`
SampleRate string `json:"sample_rate"`
Channels int `json:"channels"`
Tags map[string]string `json:"tags"`
} `json:"streams"`
}
func Probe(ctx context.Context, ffprobe, path string) (Result, error) {
b, err := exec.CommandContext(ctx, ffprobe, "-v", "error", "-show_format", "-show_streams", "-of", "json", path).CombinedOutput()
if err != nil {
return Result{}, fmt.Errorf("ffprobe: %w: %s", err, strings.TrimSpace(string(b)))
}
var p rawProbe
if err := json.Unmarshal(b, &p); err != nil {
return Result{}, err
}
tags := lowerTags(p.Format.Tags)
r := Result{Title: tags["title"], Artist: tags["artist"], AlbumArtist: tags["album_artist"], Album: tags["album"], Genre: tags["genre"], Container: strings.Split(p.Format.FormatName, ",")[0]}
r.Duration, _ = strconv.ParseFloat(p.Format.Duration, 64)
r.Bitrate, _ = strconv.ParseInt(p.Format.BitRate, 10, 64)
r.Year = leadingInt(first(tags["date"], tags["year"]))
r.Track = leadingInt(tags["track"])
r.Disc = leadingInt(tags["disc"])
for _, s := range p.Streams {
if s.CodecType == "audio" {
r.Codec = s.CodecName
r.SampleRate, _ = strconv.Atoi(s.SampleRate)
r.Channels = s.Channels
break
}
}
return r, nil
}
func ExtractCover(ctx context.Context, ffmpeg, source, destination string) error {
b, err := exec.CommandContext(ctx, ffmpeg, "-y", "-v", "error", "-i", source, "-map", "0:v:0", "-frames:v", "1", "-vf", "scale='min(600,iw)':-2", destination).CombinedOutput()
if err != nil {
return fmt.Errorf("cover: %w: %s", err, strings.TrimSpace(string(b)))
}
return nil
}
func lowerTags(in map[string]string) map[string]string {
out := map[string]string{}
for k, v := range in {
out[strings.ToLower(k)] = v
}
return out
}
func first(v ...string) string {
for _, x := range v {
if x != "" {
return x
}
}
return ""
}
func leadingInt(v string) int {
v = strings.Split(v, "/")[0]
n := 0
for _, r := range v {
if r < '0' || r > '9' {
if n > 0 {
break
}
continue
}
n = n*10 + int(r-'0')
}
return n
}