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.

Projektseite ↗
HTTPShttps://zanvex.de/git/miniaudiolib.git

internal/musicmeta/probe.go

Zum Verzeichnis
package 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
}