Skip to content

Commit 8b5243a

Browse files
authored
Album art support (#14)
* Enable album art by writing to temp * Emit file URLs only when we have a local mpd * Use socket connection if possible
1 parent f1cbde0 commit 8b5243a

File tree

5 files changed

+130
-15
lines changed

5 files changed

+130
-15
lines changed

cmd/mpd-mpris/main.go

+15
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"log"
77
"os"
8+
"path/filepath"
89
"strings"
910

1011
mpris "github.com/natsukagami/mpd-mpris"
@@ -28,12 +29,26 @@ func init() {
2829
flag.BoolVar(&noInstance, "no-instance", false, "Set the MPDris's interface as 'org.mpris.MediaPlayer2.mpd' instead of 'org.mpris.MediaPlayer2.mpd.instance#'")
2930
}
3031

32+
func detectLocalSocket() {
33+
runtimeDir, ok := os.LookupEnv("XDG_RUNTIME_DIR")
34+
if !ok {
35+
return
36+
}
37+
mpdSocket := filepath.Join(runtimeDir, "mpd/socket")
38+
if _, err := os.Stat(mpdSocket); err == nil {
39+
log.Println("local mpd socket found. using that!")
40+
network = "unix"
41+
addr = mpdSocket
42+
}
43+
}
44+
3145
func main() {
3246
flag.Parse()
3347
if len(addr) == 0 {
3448
env_host := os.Getenv("MPD_HOST")
3549
if len(env_host) == 0 {
3650
addr = "localhost"
51+
detectLocalSocket()
3752
} else {
3853
if strings.Index(env_host, "@") > -1 {
3954
addr_pwd := strings.Split(env_host, "@")

mpd/client.go

+43-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package mpd
22

33
import (
4+
"sync"
5+
46
"github.com/fhs/gompd/v2/mpd"
57
"github.com/pkg/errors"
68
)
@@ -9,7 +11,20 @@ import (
911
// Some of the methods are overriden from the `mpd.Client` struct to provide typings safety.
1012
type Client struct {
1113
*mpd.Client
12-
Address string
14+
Address string
15+
MusicDirectory string
16+
17+
lastSongMu sync.Mutex
18+
lastSong *Song
19+
}
20+
21+
func (c *Client) init() error {
22+
// Find the music directory
23+
conf, err := c.Command("config").Attrs()
24+
if err == nil {
25+
c.MusicDirectory = conf["music_directory"]
26+
}
27+
return nil
1328
}
1429

1530
// Dial connects to MPD listening on address addr (e.g. "127.0.0.1:6600") on network network (e.g. "tcp").
@@ -18,7 +33,11 @@ func Dial(network, addr string) (*Client, error) {
1833
if err != nil {
1934
return nil, errors.WithStack(err)
2035
}
21-
return &Client{Client: c, Address: addr}, nil
36+
client := &Client{Client: c, Address: addr}
37+
if err := client.init(); err != nil {
38+
return nil, err
39+
}
40+
return client, nil
2241
}
2342

2443
// DialAuthenticated connects to MPD listening on address addr (e.g. "127.0.0.1:6600") on network network (e.g. "tcp").
@@ -28,7 +47,11 @@ func DialAuthenticated(network, addr, password string) (*Client, error) {
2847
if err != nil {
2948
return nil, errors.WithStack(err)
3049
}
31-
return &Client{Client: c, Address: addr}, nil
50+
client := &Client{Client: c, Address: addr}
51+
if err := client.init(); err != nil {
52+
return nil, err
53+
}
54+
return client, nil
3255
}
3356

3457
// CurrentSong returns information about the current song in the playlist.
@@ -37,7 +60,18 @@ func (c *Client) CurrentSong() (Song, error) {
3760
if e != nil {
3861
return Song{}, errors.WithStack(e)
3962
}
40-
return SongFromAttrs(a)
63+
c.lastSongMu.Lock()
64+
defer c.lastSongMu.Unlock()
65+
if c.lastSong != nil && c.lastSong.Path() == a["file"] {
66+
// Heuristically, we have... the same song...
67+
return *c.lastSong, nil
68+
}
69+
song, err := c.SongFromAttrs(a)
70+
if err != nil {
71+
return Song{}, err
72+
}
73+
c.lastSong = &song
74+
return *c.lastSong, nil
4175
}
4276

4377
// Find searches the library for songs and returns attributes for each matching song.
@@ -54,7 +88,7 @@ func (c *Client) Find(args ...string) ([]File, error) {
5488
}
5589
arr := make([]File, len(a))
5690
for id, item := range a {
57-
if arr[id], err = FileFromAttrs(item); err != nil {
91+
if arr[id], err = c.FileFromAttrs(item); err != nil {
5892
return nil, errors.Wrapf(err, "Item %d", id)
5993
}
6094
}
@@ -70,7 +104,7 @@ func (c *Client) ListAllInfo(uri string) ([]Item, error) {
70104
}
71105
arr := make([]Item, len(a))
72106
for id, item := range a {
73-
if arr[id], err = ItemFromAttrs(item); err != nil {
107+
if arr[id], err = c.ItemFromAttrs(item); err != nil {
74108
return nil, errors.Wrapf(err, "Item %d", id)
75109
}
76110
}
@@ -85,7 +119,7 @@ func (c *Client) ListInfo(uri string) ([]Item, error) {
85119
}
86120
arr := make([]Item, len(a))
87121
for id, item := range a {
88-
if arr[id], err = ItemFromAttrs(item); err != nil {
122+
if arr[id], err = c.ItemFromAttrs(item); err != nil {
89123
return nil, errors.Wrapf(err, "Item %d", id)
90124
}
91125
}
@@ -113,7 +147,7 @@ func (c *Client) PlaylistContents(name string) ([]File, error) {
113147
}
114148
arr := make([]File, len(a))
115149
for id, item := range a {
116-
if arr[id], err = FileFromAttrs(item); err != nil {
150+
if arr[id], err = c.FileFromAttrs(item); err != nil {
117151
return nil, errors.Wrapf(err, "Item %d", id)
118152
}
119153
}
@@ -131,7 +165,7 @@ func (c *Client) PlaylistInfo(start, end int) ([]File, error) {
131165
}
132166
arr := make([]File, len(a))
133167
for id, item := range a {
134-
if arr[id], err = FileFromAttrs(item); err != nil {
168+
if arr[id], err = c.FileFromAttrs(item); err != nil {
135169
return nil, errors.Wrapf(err, "Item %d", id)
136170
}
137171
}

mpd/file.go

+12-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package mpd
22

33
import (
4+
"net/url"
5+
"path/filepath"
46
"time"
57

68
"github.com/fhs/gompd/v2/mpd"
@@ -15,9 +17,9 @@ type Item interface {
1517
}
1618

1719
// ItemFromAttrs returns an Item from the given Attr struct.
18-
func ItemFromAttrs(attr mpd.Attrs) (Item, error) {
20+
func (c *Client) ItemFromAttrs(attr mpd.Attrs) (Item, error) {
1921
if _, ok := attr["file"]; ok {
20-
return FileFromAttrs(attr)
22+
return c.FileFromAttrs(attr)
2123
}
2224
if _, ok := attr["directory"]; ok {
2325
return Directory{Attrs: attr}, nil
@@ -38,6 +40,7 @@ type File struct {
3840
AlbumArtist string
3941
Track int
4042
Duration time.Duration
43+
Filepath string // The file:// URL
4144
Attrs mpd.Attrs // Other attributes
4245
}
4346

@@ -47,9 +50,15 @@ func (f File) Path() string {
4750
}
4851

4952
// FileFromAttrs returns a File from the attributes map.
50-
func FileFromAttrs(attr mpd.Attrs) (s File, err error) {
53+
func (c *Client) FileFromAttrs(attr mpd.Attrs) (s File, err error) {
5154
p := &parseMap{m: attr}
5255

56+
if c.MusicDirectory != "" {
57+
p.String("file", &s.Filepath, false)
58+
filepath := url.URL{Scheme: "file", Path: filepath.Join(c.MusicDirectory, s.Filepath)}
59+
s.Filepath = filepath.String()
60+
}
61+
5362
if !p.String("Title", &s.Title, true) {
5463
s.Title = "unknown title"
5564
}

mpd/song.go

+55-2
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,78 @@
11
package mpd
22

33
import (
4+
"io/ioutil"
5+
"log"
6+
"os"
7+
"path/filepath"
48
"strconv"
9+
"sync"
510

611
"github.com/fhs/gompd/v2/mpd"
712
)
813

14+
var albumArtLock sync.Mutex
15+
var albumArtURI string
16+
17+
func init() {
18+
mpdTemp := filepath.Join(os.TempDir(), "mpd_mpris")
19+
if err := os.MkdirAll(mpdTemp, 0777); err != nil {
20+
log.Println("Cannot create temp file for album art, we don't support them then!", err)
21+
return
22+
}
23+
f, err := ioutil.TempFile(mpdTemp, "artwork_")
24+
if err != nil {
25+
log.Println("Cannot create temp file for album art, we don't support them then!", err)
26+
return
27+
}
28+
albumArtURI = f.Name()
29+
f.Close()
30+
}
31+
932
// Song represents a music file with metadata.
1033
type Song struct {
1134
File
1235
ID int // The song's ID (within the playlist)
36+
37+
albumArt bool // Whether the song has an album art. The album art will be loaded into memory at AlbumArtURI.
1338
}
1439

1540
// SongFromAttrs returns a song from the attributes map.
16-
func SongFromAttrs(attr mpd.Attrs) (s Song, err error) {
41+
func (c *Client) SongFromAttrs(attr mpd.Attrs) (s Song, err error) {
1742
if s.ID, err = strconv.Atoi(attr["Id"]); err != nil {
1843
s.ID = -1
1944
return s, nil
2045
}
21-
if s.File, err = FileFromAttrs(attr); err != nil {
46+
if s.File, err = c.FileFromAttrs(attr); err != nil {
2247
return
2348
}
49+
50+
if albumArtURI != "" {
51+
// Attempt to load the album art.
52+
albumArtLock.Lock()
53+
defer albumArtLock.Unlock()
54+
55+
// Write the album art to it
56+
art, err := c.AlbumArt(s.Path())
57+
if err != nil {
58+
log.Println(err)
59+
return s, nil
60+
}
61+
if err := ioutil.WriteFile(albumArtURI, art, 0x644); err != nil {
62+
log.Println(err)
63+
return s, nil
64+
}
65+
s.albumArt = true
66+
}
67+
2468
return
2569
}
70+
71+
// AlbumArtURI returns the URI to the album art, if it is available.
72+
func (s Song) AlbumArtURI() (string, bool) {
73+
if !s.albumArt {
74+
return "", false
75+
}
76+
// Should I do something better here?
77+
return "file://" + albumArtURI, true
78+
}

tracklist.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,17 @@ func MapFromSong(s mpd.Song) MetadataMap {
6262

6363
m.nonEmptyString("xesam:album", s.Album)
6464
m.nonEmptyString("xesam:title", s.Title)
65-
m.nonEmptyString("xesam:url", s.Path())
65+
m.nonEmptyString("xesam:url", s.Filepath)
6666
m.nonEmptyString("xesam:contentCreated", s.Date)
6767
m.nonEmptySlice("xesam:albumArtist", []string{s.AlbumArtist})
6868
m.nonEmptySlice("xesam:artist", []string{s.Artist})
6969
m.nonEmptySlice("xesam:artist", []string{s.Artist})
7070
m.nonEmptySlice("xesam:genre", []string{s.Genre})
7171

72+
if artURI, ok := s.AlbumArtURI(); ok {
73+
(*m)["mpris:artUrl"] = artURI
74+
}
75+
7276
if s.Track != 0 {
7377
(*m)["xesam:trackNumber"] = s.Track
7478
}

0 commit comments

Comments
 (0)