1
0
Fork 0
livestream-tools/icedreammusic/foobar2000/main.go

211 lines
5.4 KiB
Go
Raw Normal View History

2021-02-28 17:04:41 +00:00
package main
import (
"encoding/base64"
"encoding/json"
2021-02-28 17:04:41 +00:00
"fmt"
"log"
"net"
"net/http"
"net/url"
"os"
"strings"
2021-02-28 17:04:41 +00:00
"time"
"github.com/billziss-gh/cgofuse/fuse"
"github.com/dhowden/tag"
"github.com/gin-gonic/gin"
2022-03-08 08:06:39 +00:00
"gopkg.in/alecthomas/kingpin.v3-unstable"
2021-02-28 17:04:41 +00:00
"github.com/icedream/livestream-tools/icedreammusic/metacollector"
"github.com/icedream/livestream-tools/icedreammusic/tuna"
)
2022-03-08 08:06:39 +00:00
var (
cli = kingpin.New("foobar2000", "Transmit foobar2000 now playing data to Tuna.")
argMetacollectorURL = cli.Arg("metacollector-url", "Metadata collector URL (service normally runs on port 8080)").Required().URL()
argDrive = cli.Arg("mountpoint", "The mountpoint to attach to.").Default("Z:").String()
2022-03-08 08:06:39 +00:00
)
func init() {
kingpin.MustParse(cli.Parse(os.Args[1:]))
}
2021-02-28 17:04:41 +00:00
func main() {
c, fs := NewNowPlayingFilesystem()
host := fuse.NewFileSystemHost(fs)
host.SetCapReaddirPlus(true)
r := gin.Default()
r.GET("/cover/:base64Path", func(c *gin.Context) {
path := c.Params.ByName("base64Path")
pathBytes, err := base64.URLEncoding.DecodeString(path)
if err != nil {
c.JSON(500, map[string]string{"error": err.Error()})
return
}
path = string(pathBytes)
f, err := os.Open(path)
if err != nil {
c.JSON(500, map[string]string{"error": err.Error()})
return
}
defer f.Close()
// get cover if possible
fileMetadata, err := tag.ReadFrom(f)
if err != nil {
c.JSON(500, map[string]string{"error": err.Error()})
return
}
picture := fileMetadata.Picture()
if picture == nil {
c.JSON(404, map[string]string{"error": "this file has no picture"})
return
}
c.Header("Content-type", picture.MIMEType)
c.Writer.Write(picture.Data)
return
})
listener, err := net.Listen("tcp", ":0")
if err != nil {
panic(err)
}
apiAddr := &url.URL{
Scheme: "http",
Host: fmt.Sprintf("%s:%d", "127.0.0.1", listener.Addr().(*net.TCPAddr).Port),
Path: "/",
}
go http.Serve(listener, r)
httpClient := &http.Client{
Timeout: 10 * time.Second,
}
2021-02-28 17:04:41 +00:00
go func() {
tunaOutput := tuna.NewTunaOutput()
lastCoverCheckPath := ""
lastCoverCheckResult := false
lastCoverCheckTime := time.Now()
2022-03-08 08:06:39 +00:00
metacollectorClient := metacollector.NewMetaCollectorClient(*argMetacollectorURL)
2021-02-28 17:04:41 +00:00
for metadata := range c {
// log.Printf("New metadata: %+v", metadata)
status := tuna.Stopped
2021-02-28 17:04:41 +00:00
if metadata.IsPlaying {
status = tuna.Playing
2021-02-28 17:04:41 +00:00
}
tunaMetadata := &tuna.TunaData{
Title: metadata.Title,
Artists: []string{metadata.Artist},
Label: metadata.Publisher,
Status: status,
Duration: uint64(metadata.Length * 1000),
Progress: uint64(metadata.PlaybackTime * 1000),
}
if metadata.IsPlaying {
// is this playing from Spotify?
if strings.HasPrefix(metadata.Path, "spotify:") {
// Spotify covers have to be fetched via their API
// TODO - use token for API instead of old oembed way, see https://stackoverflow.com/a/18294883
u := &url.URL{
Scheme: "https",
Host: "open.spotify.com",
Path: "/oembed",
2021-02-28 17:04:41 +00:00
}
q := u.Query()
q.Add("url", metadata.Path)
u.RawQuery = q.Encode()
if resp, err := httpClient.Get(u.String()); err == nil {
oembedData := &struct {
ThumbnailURL string `json:"thumbnail_url"`
}{}
if err = json.NewDecoder(resp.Body).Decode(oembedData); err == nil {
tunaMetadata.CoverURL = oembedData.ThumbnailURL
}
}
} else {
// Check normal/other files against metacollector
hasChanged := lastCoverCheckPath != metadata.Path
fi, err := os.Stat(metadata.Path)
if err == nil {
if !hasChanged {
hasChanged = fi.ModTime().Sub(lastCoverCheckTime) > 0
}
lastCoverCheckTime = fi.ModTime()
if hasChanged {
lastCoverCheckResult = false
lastCoverCheckPath = metadata.Path
f, err := os.Open(metadata.Path)
2021-02-28 17:04:41 +00:00
if err == nil {
// get cover if possible
fileMetadata, err := tag.ReadFrom(f)
if err == nil {
if fileMetadata.Picture() != nil {
lastCoverCheckResult = true
}
} else {
log.Printf("Warning while reading tags for %s: %s", metadata.Path, err)
2021-02-28 17:04:41 +00:00
}
f.Close()
2021-02-28 17:04:41 +00:00
} else {
log.Printf("Warning while opening file %s: %s", metadata.Path, err)
2021-02-28 17:04:41 +00:00
}
}
} else {
log.Printf("Warning while stat'ing file %s: %s", metadata.Path, err)
2021-02-28 17:04:41 +00:00
}
if lastCoverCheckResult {
tunaMetadata.CoverURL = apiAddr.ResolveReference(&url.URL{
Path: fmt.Sprintf("cover/%s", base64.URLEncoding.EncodeToString([]byte(metadata.Path))),
}).String()
}
2021-02-28 17:04:41 +00:00
}
}
go func() {
// enrich metadata with metacollector
req := metacollector.MetaCollectorRequest{
Artist: tunaMetadata.Artists[0],
2021-02-28 17:04:41 +00:00
Title: tunaMetadata.Title,
}
log.Printf("Trying to enrich metadata: %+v", req)
resp, err := metacollectorClient.GetTrack(req)
2021-02-28 17:04:41 +00:00
if err == nil {
log.Println("Enriching metadata:", resp)
2021-02-28 17:04:41 +00:00
if resp.CoverURL != nil {
tunaMetadata.CoverURL = (*argMetacollectorURL).ResolveReference(&url.URL{
2021-02-28 17:41:20 +00:00
Path: *resp.CoverURL,
}).String()
2021-02-28 17:04:41 +00:00
}
tunaMetadata.Label = resp.Publisher
} else {
log.Println("Failed to enrich metadata:", err)
2021-02-28 17:04:41 +00:00
}
err = tunaOutput.Post(tunaMetadata)
if err != nil {
log.Println(err)
} /*else {
log.Printf("Tuna has received the metadata: %+v", tunaMetadata)
}*/
}()
}
}()
host.Mount("", []string{
*argDrive,
})
2021-02-28 17:04:41 +00:00
}