70 lines
1.9 KiB
Go
70 lines
1.9 KiB
Go
package metadata
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os/exec"
|
|
)
|
|
|
|
type ffprobeOutput struct {
|
|
Format ffprobeFormat `json:"format"`
|
|
Streams []ffprobeStream `json:"streams"`
|
|
}
|
|
|
|
type ffprobeFormat struct {
|
|
FormatName string `json:"format_name"`
|
|
Duration string `json:"duration"`
|
|
BitRate string `json:"bit_rate"`
|
|
Tags map[string]string `json:"tags"`
|
|
}
|
|
|
|
type ffprobeStream struct {
|
|
Index int `json:"index"`
|
|
CodecType string `json:"codec_type"` // "audio", "video", "subtitle", ...
|
|
CodecName string `json:"codec_name"`
|
|
SampleRate string `json:"sample_rate"`
|
|
Channels int `json:"channels"`
|
|
Duration string `json:"duration"`
|
|
BitRate string `json:"bit_rate"`
|
|
Disposition map[string]int `json:"disposition"`
|
|
Tags map[string]string `json:"tags"`
|
|
}
|
|
|
|
// Prober runs ffprobe to extract tags and technical properties from a
|
|
// media file.
|
|
type Prober struct {
|
|
FFprobePath string
|
|
}
|
|
|
|
// Probe reads metadata from the file at path. path must already be a
|
|
// validated, absolute filesystem path within a configured music
|
|
// folder — Probe does not perform any authorization or traversal checks
|
|
// itself.
|
|
func (p *Prober) Probe(ctx context.Context, path string) (*Tags, error) {
|
|
ffprobePath := p.FFprobePath
|
|
if ffprobePath == "" {
|
|
ffprobePath = "ffprobe"
|
|
}
|
|
cmd := exec.CommandContext(ctx, ffprobePath,
|
|
"-v", "error",
|
|
"-print_format", "json",
|
|
"-show_format",
|
|
"-show_streams",
|
|
"--", path,
|
|
)
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
if ee, ok := err.(*exec.ExitError); ok {
|
|
return nil, fmt.Errorf("ffprobe: %w: %s", err, string(ee.Stderr))
|
|
}
|
|
return nil, fmt.Errorf("ffprobe: %w", err)
|
|
}
|
|
|
|
var raw ffprobeOutput
|
|
if err := json.Unmarshal(out, &raw); err != nil {
|
|
return nil, fmt.Errorf("parsing ffprobe output: %w", err)
|
|
}
|
|
return parseFFprobeOutput(raw), nil
|
|
}
|