add ffprobe tag reader for webm, mp4, mkv
This is not well-tested tag reader. So FFProbe.Read() only success in following condition: - FFProbeScore == 100
This commit is contained in:
@@ -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
9
tags/ffprobe/errors.go
Normal 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
97
tags/ffprobe/ffprobe.go
Normal 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 }
|
||||
27
tags/ffprobe/ffprobe_output_struct.go
Normal file
27
tags/ffprobe/ffprobe_output_struct.go
Normal 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"`
|
||||
}
|
||||
Reference in New Issue
Block a user