+(defun emus-get-audio-files ()
+ "Get all mp3 files in main emus directory."
+ (mapcar
+ #'expand-file-name
+ (directory-files-recursively emus-directory ".*\\.mp3")))
+
+(defun emus-get-playlist-files ()
+ "Get all m3u files in the main emus music directory."
+ (mapcar
+ #'expand-file-name
+ (directory-files-recursively emus-directory ".*\\.m3u")))
+
+(defun emus-make-track (artist album title filename &optional pos)
+ "Create an object representing an emus track.
+ARTIST, ALBUM and TITLE are used to describe the track, FILENAME
+refers to the mp3 file containing the track. If non-nil, POS
+specifies the position of the record representing this track in the
+emus browser buffer."
+ (list artist album title filename pos))
+
+(defun emus-track-artist (track)
+ "The artist corresponding to TRACK."
+ (elt track 0))
+
+(defun emus-track-album (track)
+ "The album corresponding to TRACK."
+ (elt track 1))
+
+(defun emus-track-title (track)
+ "The title of TRACK."
+ (elt track 2))
+
+(defun emus-track-file (track)
+ "The mp3 file corresponding to TRACK."
+ (elt track 3))
+
+(defun emus-track-browser-pos (track)
+ "The location of the browser buffer record corresponding to TRACK."
+ (elt track 4))
+
+(defun emus-set-track-browser-pos (track pos)
+ "Set the location of the browser buffer record corresponding to TRACK to POS."
+ (setf (seq-elt track 4) pos))
+
+(defun emus--load-library (then)
+ "Initialize the emus track library.
+Once the library is initialized, the function THEN is called."
+ (unless emus--proc-in-use
+ (setq emus--proc-in-use t)
+ (emus--suspend-cp)
+ (setq emus-state 'stopped)
+ (setq emus-tracks nil)
+ (let ((proc (emus-get-process))
+ (tagstr "")
+ (filenames (emus-get-audio-files)))
+ (set-process-filter proc (lambda (proc string)
+ (setq tagstr (concat tagstr string))
+ (when (string-suffix-p "@P 1\n" string)
+ (add-to-list 'emus-tracks
+ (emus--make-track-from-tagstr (car filenames)
+ tagstr))
+ (setq tagstr "")
+ (setq filenames (cdr filenames))
+ (if filenames
+ (emus--send-cmd-raw "lp" (car filenames))
+ (set-process-filter proc nil)
+ (setq emus-tracks (reverse emus-tracks))
+ (emus--sort-tracks)
+ (emus--add-tracks-from-playlist-files)
+ (unless emus-current-track
+ (setq emus-current-track (car emus-tracks)))
+ (funcall then)
+ (emus--resume-cp)
+ (setq emus--proc-in-use nil)))))
+ (emus--send-cmd-raw "lp" (car filenames)))))
+
+(defun emus--dump-tracks ()
+ emus-tracks)
+
+(defun emus--make-track-from-tagstr (filename tagstr)
+ "Parse TAGSTR to populate the fields of a track corresponding to FILENAME."
+ (let ((artist "Unknown Artist")
+ (album "Unknown Album")
+ (title filename))
+ (dolist (line (split-string tagstr "\n"))
+ (let ((found-artist (elt (split-string line "@I ID3v2.artist:") 1))
+ (found-album (elt (split-string line "@I ID3v2.album:") 1))
+ (found-title (elt (split-string line "@I ID3v2.title:") 1)))
+ (cond
+ (found-artist (setq artist found-artist))
+ (found-album (setq album found-album))
+ (found-title (setq title found-title)))))
+ (emus-make-track artist album title filename)))
+
+(defun emus--add-tracks-from-playlist-files ()
+ (let ((tracks nil))
+ (dolist (filename (emus-get-playlist-files))
+ (let ((artist "Playlists")
+ (album (file-name-base filename))
+ (title nil)
+ (lines (split-string (with-temp-buffer
+ (insert-file-contents filename)
+ (buffer-string))
+ "\n")))
+ (dolist (line lines)
+ (pcase (string-trim line)
+ ((rx (: string-start
+ (* space)
+ "#extinf:"
+ (* (not ",")) ","
+ (let display-title (* any))
+ string-end))
+ (setq title display-title))
+ ((rx (: string-start (* space) "#"))) ;skip other comments
+ ((rx (let filename (+ any)))
+ (setq tracks (cons (emus-make-track artist album (or title filename) filename)
+ tracks))
+ (setq title nil))))))
+ (setq emus-tracks (append emus-tracks (reverse tracks)))))
+
+(defun emus--sort-tracks ()
+ "Sort the library tracks according to artist and album.
+Leaves the track titles unsorted, so they will appear in the order specified
+by the filesystem."
+ (setq emus-tracks
+ (sort emus-tracks
+ (lambda (r1 r2)
+ (let ((artist1 (emus-track-artist r1))
+ (artist2 (emus-track-artist r2)))
+ (if (string= artist1 artist2)
+ (let ((album1 (emus-track-album r1))
+ (album2 (emus-track-album r2)))
+ (string< album1 album2))
+ (string< artist1 artist2)))))))
+
+(defmacro emus--with-library (&rest body)
+ "Evaluate BODY with the library initialized."
+ `(if emus-tracks
+ (unless emus--proc-in-use ,@body)
+ (emus--load-library
+ (lambda () ,@body))))
+