Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.org
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,17 @@ The library is implemented around a set of entities.
| *Name* | *Type* | *Fields* | *Description* |
|------------------+-----------+----------------------+---------------------------------------------------------|
| song | structure | name, album, file, … | |
| album | structure | name, artist | |
| album | structure | name, date, artists | |
| artist | structure | name | |
| genre | structure | name | |
| directory | structure | name, path | |
| stored-playlist | structure | name | A named user-specified sequence of songs |
| search-criteria | structure | type, what | Read the [[https://www.musicpd.org/doc/protocol/database.html][protocol documentation]] |
| filter | structure | text | Read the [[https://www.musicpd.org/doc/html/protocol.html#filters][protocol documentation]] |
|------------------+-----------+----------------------+---------------------------------------------------------|
| artists | symbol | /none/ | Represent the set of all artists |
| albums | symbol | /none/ | Represent the set of all albums |
| genres | symbol | /none/ | Represent the set of all genres |
| directories | symbol | /none/ | Represent all directories in ~libmpdel-music-directory~ |
| current-playlist | symbol | /none/ | Represent the currently played sequence of songs |
| stored-playlists | symbol | /none/ | Represent the set of all stored playlists |
Expand Down
149 changes: 123 additions & 26 deletions libmpdel.el
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
(require 'tq)
(require 'cl-lib)
(require 'subr-x)
(require 'seq)


;;; Customization
Expand Down Expand Up @@ -171,7 +172,8 @@ message from the server.")
(:constructor libmpdel--album-create)
(:conc-name libmpdel--album-))
(name nil :read-only t)
(artist nil :read-only t))
(date nil :read-only t)
(artists nil :read-only t))

(cl-defstruct (libmpdel-song
(:constructor libmpdel--song-create)
Expand All @@ -180,6 +182,9 @@ message from the server.")
(track nil :read-only t)
(file nil :read-only t)
(album nil :read-only t)
(genres nil :read-only t)
(performers nil :read-only t)
(artists nil :read-only t)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how is the value of "artists" different from the album's artists?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MPD distinguishes between "AlbumArtist" and "Artist", and this reflects this distinction in libmpdel. Typical real world examples of when these differ are:

  • artists that feature on an individual track of an album (these will not be in the AlbumArtist but will be in the Artist field), or
  • compilation albums (in this case, the AlbumArtist is typically the compiler), or
  • split albums (here, all tracks typically list both AlbumArtists but only one Artist)

(disc nil :read-only t)
(date nil :read-only t)
(id nil :read-only t)
Expand All @@ -190,6 +195,11 @@ message from the server.")
(:conc-name libmpdel--stored-playlist-))
(name nil :read-only t))

(cl-defstruct (libmpdel-genre
(:constructor libmpdel--genre-create)
(:conc-name libmpdel--genre-))
(name nil :read-only t))

(cl-defstruct (libmpdel-search-criteria
(:constructor libmpdel-search-criteria-create)
(:conc-name libmpdel--search-criteria-))
Expand All @@ -205,20 +215,28 @@ message from the server.")
"Return artist name of ENTITY."
(libmpdel--artist-name (libmpdel-artist entity)))

(defun libmpdel-artists-name (entity)
"Return semicolon separated string of artists names of ENTITY."
(string-join (mapcar #'libmpdel--artist-name
(libmpdel-artists entity))
"; "))

(cl-defgeneric libmpdel-artist (entity)
"Return artist of ENTITY.")
"Return artist of ENTITY."
(or (car (libmpdel-artists entity))
libmpdel--unknown-artist))

(cl-defmethod libmpdel-artist ((artist libmpdel-artist))
"Return ARTIST."
artist)
(cl-defmethod libmpdel-artists ((artist libmpdel-artist))
"Return singleton list containing ARTIST."
(list artist))

(cl-defmethod libmpdel-artist ((album libmpdel-album))
(cl-defmethod libmpdel-artists ((album libmpdel-album))
"Return the ALBUM's artist."
(libmpdel--album-artist album))
(libmpdel--album-artists album))

(cl-defmethod libmpdel-artist ((song libmpdel-song))
"Return the SONG's artist."
(libmpdel-artist (libmpdel--song-album song)))
(cl-defmethod libmpdel-artists ((song libmpdel-song))
"Return the SONG's artists."
(libmpdel--song-artists song))

(defun libmpdel-album-name (entity)
"Return album name of ENTITY."
Expand All @@ -235,6 +253,18 @@ message from the server.")
"Return SONG's album."
(libmpdel--song-album song))

(cl-defmethod libmpdel-genres ((song libmpdel-song))
"Return SONG's genres."
(libmpdel--song-genres song))

(cl-defmethod libmpdel-genres ((genre libmpdel-genre))
"Return singleton list GENRE."
(list genre))

(cl-defmethod libmpdel-performers ((song libmpdel-song))
"Return SONG's performers."
(libmpdel--song-performers song))

(cl-defgeneric libmpdel-entity-name (entity)
"Return the name of ENTITY.")

Expand All @@ -246,6 +276,10 @@ message from the server.")
"Return ALBUM's name."
(libmpdel--album-name album))

(cl-defmethod libmpdel-entity-name ((genre libmpdel-genre))
"Return GENRE's name."
(libmpdel--genre-name genre))

(cl-defmethod libmpdel-entity-name ((song libmpdel-song))
"Return SONG's name.

Expand All @@ -265,6 +299,10 @@ If the SONG's name is nil, return the filename instead."
"Return a string describing the `albums' entity."
"All albums")

(cl-defmethod libmpdel-entity-name ((_entity (eql genres)))
"Return a string describing the `genres' entity."
"All genres")

(cl-defmethod libmpdel-entity-name ((_entity (eql current-playlist)))
"Return a string describing the `current-playlist' entity."
"Current playlist")
Expand Down Expand Up @@ -295,6 +333,10 @@ If the SONG's name is nil, return the filename instead."
"Return ALBUM's artist."
(libmpdel-artist album))

(cl-defmethod libmpdel-entity-parent ((_genre libmpdel-genre))
"Return the `genres' entity."
'genres)

(cl-defmethod libmpdel-entity-parent ((_artist libmpdel-artist))
"Return the `artists' entity."
'artists)
Expand All @@ -321,9 +363,16 @@ If the SONG's name is nil, return the filename instead."
"Return the track number of SONG within its album."
(or (libmpdel--song-track song) ""))

(defun libmpdel-entity-date (song)
"Return the date of SONG."
(or (libmpdel--song-date song) ""))
(cl-defgeneric libmpdel-entity-date (entity)
"Return the date of ENTITY.")

(cl-defmethod libmpdel-entity-date ((album libmpdel-album))
"Return ALBUM's date."
(libmpdel--album-date album))

(cl-defmethod libmpdel-entity-date ((song libmpdel-song))
"Return SONG's date."
(libmpdel--song-date song))

(defun libmpdel-song-disc (song)
"Return the disc number of SONG within its album."
Expand All @@ -339,15 +388,28 @@ If the SONG's name is nil, return the filename instead."
(when (and (stringp pos) (not (string= pos "")))
(string-to-number pos))))

(defun libmpdel--artists-create (artist-names)
"Return a list of artists whose names are ARTIST-NAMES."
(mapcar (lambda (name)
(libmpdel--artist-create :name name))
artist-names))

(defun libmpdel--genres-create (genre-names)
"Return a list of genres whose names are GENRE-NAMES."
(mapcar (lambda (name)
(libmpdel--genre-create :name name))
genre-names))

(defun libmpdel--create-song-from-data (song-data)
"Return a song from SONG-DATA, a server's response."
(libmpdel--song-create
:name (cdr (assq 'Title song-data))
:track (cdr (assq 'Track song-data))
:file (cdr (assq 'file song-data))
:album (libmpdel--album-create
:name (cdr (assq 'Album song-data))
:artist (libmpdel--artist-create :name (cdr (assq 'Artist song-data))))
:genres (libmpdel--genres-create (libmpdel-entries song-data 'Genre))
:performers (libmpdel--artists-create (libmpdel-entries song-data 'Performer))
:artists (libmpdel--artists-create (libmpdel-entries song-data 'Artist))
:album (libmpdel--create-album-from-data song-data)
:date (cdr (assq 'Date song-data))
:disc (cdr (assq 'Disc song-data))
:id (cdr (assq 'Id song-data))
Expand All @@ -357,6 +419,17 @@ If the SONG's name is nil, return the filename instead."
"Return a list of songs from DATA, a server's response."
(mapcar #'libmpdel--create-song-from-data (libmpdel-group-data data)))

(defun libmpdel--create-album-from-data (album-data)
"Return an album from ALBUM-DATA, a server's response."
(libmpdel--album-create
:name (cdr (assq 'Album album-data))
:date (cdr (assq 'Date album-data))
:artists (libmpdel--artists-create (libmpdel-entries album-data 'AlbumArtist))))

(defun libmpdel--create-albums-from-data (data)
"Return a list of albums from DATA, a server's response."
(mapcar #'libmpdel--create-album-from-data (libmpdel-group-data data)))

(defun libmpdel-current-playlist-p (entity)
"Return non-nil if ENTITY is the current playlist."
(eq entity 'current-playlist))
Expand Down Expand Up @@ -920,12 +993,26 @@ If HANDLER is nil, ignore response."
(cl-defmethod libmpdel-entity-to-criteria ((album libmpdel-album))
"Return search query matching all songs from ALBUM."
(format "%s album %S"
(libmpdel-entity-to-criteria (libmpdel-artist album))
(string-join
(mapcar (lambda (artist)
(format "albumartist %S" (libmpdel-entity-name artist)))
(libmpdel-artists album))
" ")
(libmpdel-entity-name album)))

(cl-defmethod libmpdel-entity-to-criteria ((genre libmpdel-genre))
"Return search query matching all songs from GENRE."
(format "genre %S"
(libmpdel-entity-name genre)))

(cl-defmethod libmpdel-entity-to-criteria ((song libmpdel-song))
"Return search query matching SONG."
(format "%s title %S"
(format "%s %s title %S"
(string-join
(mapcar (lambda (artist)
(format "artist %S" (libmpdel-entity-name artist)))
(libmpdel-artists song))
" ")
(libmpdel-entity-to-criteria (libmpdel-album song))
(libmpdel-entity-name song)))

Expand All @@ -950,14 +1037,24 @@ If HANDLER is nil, ignore response."
(cl-defmethod libmpdel-list ((_entity (eql albums)) function)
"Call FUNCTION with all albums as parameter."
(libmpdel-send-command
;; TODO: compute all albums by listing all songs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the purpose of this TODO? Are you planning to address it before we get the PR merged?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit of a tradeoff that still needs to be addressed: you cannot get a correct list of all albums along with the associated dates and albumartists using list album (it might be tempting to do something along the lines of list album group albumartist group date, but that duplicates albums with several albumartists).

As far as I know, if you want a list of all albums with metadata, you'd need to list all the songs in the entire database using find and filter them into albums, analogously to what libmpdel-list ((artist libmpdel-artist) function) does in this PR.

I think that this tradeoff is worth it for artists, since listing all the songs of a particular artist should be an acceptable computational expense - but listing and processing all the songs in the mpd database may however very well be prohibitively computationally expensive.

So the tradeoff is:

  • either mpdel-core-open-albums lists the albums without artist and date metadata, effectively merging any albums with the same name (this is the current situation in this PR),
  • or running mpdel-core-open-albums is quite expensive if you have a large MPD library

"list album"
(lambda (data)
(funcall function
(seq-uniq
(libmpdel--create-albums-from-data data)
#'libmpdel-equal)))))

(cl-defmethod libmpdel-list ((_entity (eql genres)) function)
"Call FUNCTION with all genres as parameter."
(libmpdel-send-command
"list genre"
(lambda (data)
(funcall function
(mapcar
(lambda (album-name)
(libmpdel--album-create :name album-name
:artist libmpdel--unknown-artist))
(libmpdel-sorted-entries data 'Album))))))
(lambda (genre-name)
(libmpdel--genre-create :name genre-name))
(libmpdel-sorted-entries data 'Genre))))))

(cl-defmethod libmpdel-list ((_entity (eql stored-playlists)) function)
"Call FUNCTION with all stored playlists as parameter."
Expand All @@ -972,12 +1069,12 @@ If HANDLER is nil, ignore response."
(cl-defmethod libmpdel-list ((artist libmpdel-artist) function)
"Call FUNCTION with all albums of ARTIST as parameter."
(libmpdel-send-command
`("list album %s" ,(libmpdel-entity-to-criteria artist))
`("find %s sort AlbumSort" ,(libmpdel-entity-to-criteria artist))
(lambda (data)
(funcall function
(mapcar
(lambda (album-name) (libmpdel--album-create :name album-name :artist artist))
(libmpdel-sorted-entries data 'Album))))))
(seq-uniq
(libmpdel--create-albums-from-data data)
#'libmpdel-equal)))))

(cl-defgeneric libmpdel-list-songs (entity function)
"Call FUNCTION with all songs of ENTITY."
Expand Down
Loading