From 78dc143e4585a3ab5a4d173f50571b4842a4e347 Mon Sep 17 00:00:00 2001 From: Florian Fischer Date: Mon, 31 Jul 2023 10:23:22 +0200 Subject: implement initial loading of spotify playlists Playlist can now be added to the initial play queue with the -playlists command line flag or the playlists yaml key in the config file. To load a spotify playlist the link obtained when sharing the playlist from spotify is used. --- TODO.md | 2 +- config.go | 1 + main.go | 5 +++++ player.go | 9 +++++++++ spotify.go | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ wrms.go | 30 ++++++++++++++++++++++++++---- 6 files changed, 95 insertions(+), 5 deletions(-) diff --git a/TODO.md b/TODO.md index 14f5707..798a1e7 100644 --- a/TODO.md +++ b/TODO.md @@ -39,7 +39,7 @@ - [X] Fix youtube search - [ ] Support loading playlists - [ ] m3u - - [ ] spotify playlist + - [X] spotify playlist ## Configuration diff --git a/config.go b/config.go index 32b94ca..b2df159 100644 --- a/config.go +++ b/config.go @@ -14,6 +14,7 @@ import ( type Config struct { Port int `yaml:"port"` Backends []string `yaml:"backends"` + Playlists []string `yaml:"playlists"` LocalMusicDir string `yaml:"music-dir"` UploadDir string `yaml:"upload-dir"` LogLevel string `yaml:"loglevel"` diff --git a/main.go b/main.go index 8c70976..c627894 100644 --- a/main.go +++ b/main.go @@ -275,12 +275,17 @@ func main() { flag.StringVar(&config.LocalMusicDir, "serve-music-dir", config.LocalMusicDir, "local music directory to serve") flag.StringVar(&config.UploadDir, "upload-dir", config.UploadDir, "directory to upload songs to") + playlists := flag.String("playlists", "", "playlists to load") flag.Parse() if *backends != "" { config.Backends = strings.Split(*backends, " ") } + if *playlists != "" { + config.Playlists = append(config.Playlists, strings.Split(*playlists, " ")...) + } + llog.SetLogLevelFromString(config.LogLevel) config.HasUpload = slices.Contains(config.Backends, "upload") diff --git a/player.go b/player.go index 795978b..b8db8fa 100644 --- a/player.go +++ b/player.go @@ -20,6 +20,7 @@ type Player interface { Pause() Continue() Stop() + LoadPlaylist(playlist string) []*Song } // command struct used to serialize player commands @@ -253,3 +254,11 @@ func (player *MpvPlayer) Search(pattern map[string]string) chan []*Song { return ch } + +func (player *MpvPlayer) LoadPlaylist(playlist string) (songs []*Song) { + if strings.Contains(playlist, "spotify.com") { + songs = player.Backends["spotify"].(*SpotifyBackend).loadPlaylist(playlist) + } + + return +} diff --git a/spotify.go b/spotify.go index 31adf1b..3e8d962 100644 --- a/spotify.go +++ b/spotify.go @@ -194,3 +194,56 @@ func (spotify *SpotifyBackend) Search(patterns map[string]string) []*Song { } return results } + +func (spotify *SpotifyBackend) loadPlaylist(playlist string) []*Song { + playlistIdRegex := regexp.MustCompile(`playlist/.*\?`) + match := playlistIdRegex.FindString(playlist) + if match == "" { + llog.Error("Failed to extract spotify playlist id from %s", playlist) + } + + id := match[len("playlist/") : len(match)-1] + llog.Debug("Extracted id %s from spotify playlist URI %s", id, playlist) + + // _pl, err := GetPlaylistV2(spotify.session.Mercury(), id) + _pl, err := spotify.session.Mercury().GetPlaylist(id) + if err != nil { + llog.Error("Getting playlist %s failed with %v", playlist, err) + return nil + } + + plContent := _pl.GetContents() + if plContent == nil { + llog.Warning("Playlist %s has no content", playlist) + return nil + } + + plItems := plContent.GetItems() + if plItems == nil { + llog.Warning("Playlist %s content has no items", playlist) + return nil + } + + var songs []*Song + for _, item := range plItems { + uri := item.GetUri() + if uri == "" { + continue + } + + + uriParts := strings.Split(uri, ":") + uri = uriParts[len(uriParts)-1] + track, err := spotify.session.Mercury().GetTrack(utils.Base62ToHex(uri)) + if err != nil { + llog.Error("Failed to load track %s: %v", uri, err) + } + + s := NewSong(*track.Name, *track.Artist[0].Name, "spotify", uri) + s.Album = *track.Album.Name + songs = append(songs, s) + } + + llog.Debug("Added %d songs from spotify playlist %s", len(songs), _pl.Attributes.GetName()) + return songs +} diff --git a/wrms.go b/wrms.go index 247ede8..5dfdc53 100644 --- a/wrms.go +++ b/wrms.go @@ -54,6 +54,11 @@ func NewWrms(config Config) *Wrms { wrms := Wrms{} wrms.Config = config wrms.Player = NewMpvPlayer(&wrms, config.Backends) + + if len(wrms.Config.Playlists) > 0 { + wrms.loadPlaylists(wrms.Config.Playlists) + } + return &wrms } @@ -134,6 +139,11 @@ func (wrms *Wrms) Broadcast(ev Event) { }) } +func (wrms *Wrms) _addSong(song *Song) { + wrms.Songs = append(wrms.Songs, song) + wrms.queue.Add(song) +} + func (wrms *Wrms) AddSong(song *Song) { wrms.rwlock.Lock() @@ -144,14 +154,12 @@ func (wrms *Wrms) AddSong(song *Song) { wrms.queue.applyTimeBonus(wrms.Config.timeBonus) } - wrms.Songs = append(wrms.Songs, song) - s := wrms.Songs[len(wrms.Songs)-1] - wrms.queue.Add(s) + wrms._addSong(song) ev := wrms.newEvent("add", []*Song{song}) wrms.rwlock.Unlock() - llog.Info("Added song %s (ptr=%p) to Songs", s.Uri, s) + llog.Info("Added song %s (ptr=%p) to Songs", song.Uri, song) wrms.Broadcast(ev) if startPlayingAgain { @@ -343,3 +351,17 @@ func (wrms *Wrms) AdjustSongWeight(connId uuid.UUID, songUri string, vote string func (wrms *Wrms) Search(pattern map[string]string) chan []*Song { return wrms.Player.Search(pattern) } + +func (wrms *Wrms) loadPlaylists(playlists []string) { + for _, playlist := range playlists { + wrms.appendPlaylist(playlist) + } +} + +func (wrms *Wrms) appendPlaylist(playlist string) { + songs := wrms.Player.LoadPlaylist(playlist) + + for _, song := range songs { + wrms._addSong(song) + } +} -- cgit v1.2.3