add ffprobe tag reader for webm, mp4, mkv
Some checks failed
Release / Lint and test (push) Failing after 17m42s
Release / Run Release Please (push) Has been skipped
Release / Build, tag, and publish Docker image (push) Has been skipped
Release / Notify IRC (push) Has been skipped

This is not well-tested tag reader. So FFProbe.Read() only success in
following condition:
- FFProbeScore == 100
This commit is contained in:
2024-07-01 15:17:56 +08:00
parent e8e478f2aa
commit 02be9219b1
4 changed files with 135 additions and 1 deletions

View File

@@ -45,6 +45,7 @@ import (
"go.senan.xyz/gonic/scrobble"
"go.senan.xyz/gonic/server/ctrladmin"
"go.senan.xyz/gonic/server/ctrlsubsonic"
"go.senan.xyz/gonic/tags/ffprobe"
"go.senan.xyz/gonic/tags/tagcommon"
"go.senan.xyz/gonic/tags/taglib"
"go.senan.xyz/gonic/transcode"
@@ -185,7 +186,7 @@ func main() {
tagReader := tagcommon.ChainReader{
taglib.TagLib{},
// ffprobe reader?
ffprobe.FFProbe{},
// nfo reader?
}

9
tags/ffprobe/errors.go Normal file
View File

@@ -0,0 +1,9 @@
package ffprobe
import "errors"
var (
ErrNoMediaStreams = errors.New("no media streams")
ErrNoMediaDuration = errors.New("no media duration")
ErrFFProbeScroeNotEnough = errors.New("ffprobe score not enough")
)

97
tags/ffprobe/ffprobe.go Normal file
View File

@@ -0,0 +1,97 @@
package ffprobe
import (
"bytes"
"encoding/json"
"os/exec"
"path/filepath"
"strconv"
"strings"
"go.senan.xyz/gonic/tags/tagcommon"
)
type FFProbe struct{}
func (FFProbe) CanRead(absPath string) bool {
switch ext := strings.ToLower(filepath.Ext(absPath)); ext {
case ".webm", ".mp4", ".mkv":
return true
}
return false
}
func (FFProbe) Read(absPath string) (tagcommon.Info, error) {
cmd := exec.Command(
"ffprobe",
"-v", "quiet",
"-print_format", "json",
"-show_format",
"-show_streams",
)
stdout := bytes.NewBuffer(make([]byte, 0, 1024))
stderr := bytes.NewBuffer(make([]byte, 0, 1024))
cmd.Stdout = stdout
cmd.Stderr = stderr
cmd.Args = append(cmd.Args, absPath)
if err := cmd.Run(); err != nil {
return nil, err
}
var mi MediaInfo
if err := json.NewDecoder(stdout).Decode(&mi); err != nil {
return nil, err
}
if len(mi.Streams) == 0 {
return nil, ErrNoMediaStreams
}
if mi.Format.ProbeScore < 100 {
return nil, ErrFFProbeScroeNotEnough
}
if mi.Format.Duration == "" {
return nil, ErrNoMediaDuration
}
ret := &info{absPath, mi}
return ret, nil
}
type info struct {
abspath string
mediaInfo MediaInfo
}
func (i *info) Title() string { return "" }
func (i *info) BrainzID() string { return "" }
func (i *info) Artist() string { return tagcommon.FallbackArtist }
func (i *info) Artists() []string { return []string{tagcommon.FallbackArtist} }
func (i *info) Album() string { return "" }
func (i *info) AlbumArtist() string { return "" }
func (i *info) AlbumArtists() []string { return []string{} }
func (i *info) AlbumBrainzID() string { return "" }
func (i *info) Genre() string { return tagcommon.FallbackGenre }
func (i *info) Genres() []string { return []string{tagcommon.FallbackGenre} }
func (i *info) TrackNumber() int { return 0 }
func (i *info) DiscNumber() int { return 0 }
func (i *info) Year() int { return 0 }
func (i *info) ReplayGainTrackGain() float32 { return 0 }
func (i *info) ReplayGainTrackPeak() float32 { return 0 }
func (i *info) ReplayGainAlbumGain() float32 { return 0 }
func (i *info) ReplayGainAlbumPeak() float32 { return 0 }
func (i *info) Length() int {
ret, _ := strconv.ParseFloat(i.mediaInfo.Format.Duration, 64)
return int(ret)
}
func (i *info) Bitrate() int {
ret, _ := strconv.Atoi(i.mediaInfo.Format.BitRate)
return ret
}
func (i *info) AbsPath() string { return i.abspath }

View File

@@ -0,0 +1,27 @@
package ffprobe
type Stream struct {
Index int `json:"index"`
CodecName string `json:"codec_name"`
CodecLongName string `json:"codec_long_name"`
CodecType string `json:"codec_type"`
SampleRate string `json:"sample_rate"`
Channels int `json:"channels"`
ChannelLayout string `json:"channel_layout"`
}
type Format struct {
Filename string `json:"filename"`
NbStreams int `json:"nb_streams"`
FormatName string `json:"format_name"`
FormatLongName string `json:"format_long_name"`
Duration string `json:"duration"`
Size string `json:"size"`
BitRate string `json:"bit_rate"`
ProbeScore int `json:"probe_score"`
}
type MediaInfo struct {
Streams []Stream `json:"streams"`
Format Format `json:"format"`
}