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