;;; jao-mpc-random-album.el --- random mpc albums -*- lexical-binding: t; -*- ;; Copyright (C) 2021 jao ;; Author: jao ;; Keywords: convenience ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; Simple mpd control using elmpd and mpc. ;;; Code: (require 'elmpd) (require 'jao-minibuffer) (require 'jao-lyrics) (defvar jao-mpc--connection nil) (defvar-local jao-mpc--local-connecton nil) (defvar-local jao-mpc--local-port nil) (defvar jao-mpc-host "localhost") (defvar jao-mpc-port 6600) (defun jao-mpc-disconnect () (interactive) (when jao-mpc--connection (delete-process (elmpd-connection--fd jao-mpc--connection)) (setq jao-mpc--connection nil))) (defun jao-mpc--connect (name &optional cb) (elmpd-connect :name name :host jao-mpc-host :port jao-mpc-port :subsystems (when cb `((player) . ,cb)))) (defun jao-mpc-connect (&optional force) (interactive) (when force (jao-mpc-disconnect)) (unless jao-mpc--connection (setq jao-mpc--connection (jao-mpc--connect "jao-mpc" 'jao-mpc--watcher)) (jao-mpc--watcher jao-mpc--connection 'player)) jao-mpc--connection) (defun jao-mpc--send (cmd cb) (elmpd-send jao-mpc--connection cmd cb )) (defvar jao-mpc--play-status '()) (defvar jao-mpc--current '()) (defvar jao-mpc-minibuffer-str "") (defun jao-mpc--parse-retort (txt) (let (res) (dolist (e (split-string txt "\n" t " ") res) (let ((e (split-string e ": " t " "))) (when (and (car e) (cadr e)) (push (cons (car e) (cadr e)) res)))))) (defun jao-mpc--update-status (next) (let ((cb (lambda (_c ok txt) (when ok (setq jao-mpc--play-status (jao-mpc--parse-retort txt)) (when next (funcall next)))))) (jao-mpc--send "status" cb))) (defun jao-mpc--current-get (x &optional def) (alist-get x jao-mpc--current def nil #'string=)) (defun jao-mpc--status-get (x &optional def) (alist-get x jao-mpc--play-status def nil #'string=)) (defun jao-mpc--playing-p () (string= (jao-mpc--status-get "state" "") "play")) (defun jao-mpc--current-str () (let ((title (jao-mpc--current-get "Title")) (album (jao-mpc--current-get "Album")) (no (string-to-number (jao-mpc--current-get "Track" "0"))) (len (string-to-number (jao-mpc--status-get "playlistlength" "1"))) (artist (jao-mpc--current-get "Artist")) (composer (jao-mpc--current-get "Composer"))) (format " %s%s %s%s%s" (jao--put-face (if (zerop no) "" (format "%02d/%s " no len)) 'jao-themes-f02) (jao--put-face title 'jao-themes-f00) (jao--put-face artist 'jao-themes-f01) (jao--put-face (if composer (format " [%s]" composer) "") 'jao-themes-f01) (jao--put-face (if album (format " (%s)" album) "") 'jao-themes-f11)))) (defun jao-mpc--update-minibuffer () (setq jao-mpc-minibuffer-str (if (jao-mpc--playing-p) (jao-mpc--current-str) "")) (jao-minibuffer-refresh)) (defun jao-mpc--update-current (&optional next) (let ((cb (lambda (_c ok txt) (when ok (setq jao-mpc--current (jao-mpc--parse-retort txt)) (jao-mpc--update-minibuffer) (when next (funcall next)))))) (jao-mpc--send "currentsong" cb))) (defun jao-mpc--watcher (_conn _subsys) (jao-mpc--update-status #'jao-mpc--update-current)) (defconst jao-mpc--albums "*MPC Albums*") (defconst jao-mpc--playlist "*MPC Playlist*") (define-derived-mode jao-mpc-albums-mode fundamental-mode "MPC Albums" "Mode to display the list of albums known by mpd." (read-only-mode -1) (delete-region (point-min) (point-max)) (insert (shell-command-to-string "mpc list album")) (goto-char (point-min)) (read-only-mode 1)) (define-key jao-mpc-albums-mode-map (kbd "n") #'next-line) (define-key jao-mpc-albums-mode-map (kbd "p") #'previous-line) (define-key jao-mpc-albums-mode-map (kbd "RET") #'jao-mpc-add-and-play) (define-key jao-mpc-albums-mode-map (kbd "q") #'bury-buffer) (defun jao-mpc--album-buffer () (if-let (b (get-buffer jao-mpc--albums)) b (with-current-buffer (get-buffer-create jao-mpc--albums) (jao-mpc-albums-mode) (current-buffer)))) (defun jao-mpc--add-and-play (&optional album) (interactive) (let* ((album (or album (string-trim (thing-at-point 'line)))) (cmd (format "mpc findadd album \"%s\" && mpc play" album))) (shell-command-to-string "mpc clear") (shell-command-to-string cmd))) (define-derived-mode jao-mpc-playlist-mode fundamental-mode "MPC Playlist" "Mode to display the list of playlist known by mpd." (read-only-mode -1) (delete-region (point-min) (point-max)) (insert (shell-command-to-string (format "mpc -p %s playlist" jao-mpc-port))) (goto-char (point-min)) (display-line-numbers-mode) (read-only-mode 1)) (defun jao-mpc--playlist-goto-current () (interactive) (let ((jao-mpc--connection (or jao-mpc--local-connecton jao-mpc--local-connecton))) (jao-mpc--update-current (lambda () (when-let (no (string-to-number (jao-mpc--current-get "Pos"))) (goto-char (point-min)) (forward-line no)))))) (defun jao-mpc--playlist-play () (interactive) (shell-command-to-string (format "mpc -p %s play %s" (or jao-mpc--local-port jao-mpc-port) (line-number-at-pos)))) (define-key jao-mpc-playlist-mode-map (kbd "n") #'next-line) (define-key jao-mpc-playlist-mode-map (kbd "p") #'previous-line) (define-key jao-mpc-playlist-mode-map (kbd "q") #'bury-buffer) (define-key jao-mpc-playlist-mode-map (kbd ".") #'jao-mpc--playlist-goto-current) (define-key jao-mpc-playlist-mode-map (kbd "RET") #'jao-mpc--playlist-play) (defun jao-mpc--playlist-buffer () (with-current-buffer (get-buffer-create jao-mpc--playlist) (jao-mpc-playlist-mode) (current-buffer))) ;;;###autoload (defun jao-mpc-stop () (interactive) (shell-command-to-string "mpc stop")) ;;;###autoload (defun jao-mpc-toggle () (interactive) (jao-mpc--send "pause" nil)) ;;;###autoload (defun jao-mpc-play () (interactive) (jao-mpc--send "play" nil)) ;;;###autoload (defun jao-mpc-next () (interactive) (jao-mpc--send "next" nil)) ;;;###autoload (defun jao-mpc-previous () (interactive) (jao-mpc--send "previous" nil)) ;;;###autoload (defun jao-mpc-clear () (interactive) (jao-mpc--send "clear" nil)) ;;;###autoload (defun jao-mpc-echo-current () (interactive) (jao-notify (jao-mpc--current-str))) ;;;###autoload (defun jao-mpc-show-albums () "Show album list." (interactive) (pop-to-buffer (jao-mpc--album-buffer))) ;;;###autoload (defun jao-mpc-show-playlist () "Show current playlist." (interactive) (pop-to-buffer (jao-mpc--playlist-buffer)) (setq-local jao-mpc--local-connecton jao-mpc--connection jao-mpc--local-port jao-mpc-port) (jao-mpc--playlist-goto-current)) ;;;###autoload (defun jao-mpc-lyrics-track-data () (when-let* ((title (jao-mpc--current-get "Title")) (artist (jao-mpc--current-get "Artist"))) (cons (substring-no-properties title) (substring-no-properties artist)))) ;;;###autoload (defun jao-mpc-setup () (setq jao-lyrics-info-function #'jao-mpc-lyrics-track-data) (jao-random-album-setup #'jao-mpc--album-buffer #'jao-mpc--add-and-play #'jao-mpc-stop) (jao-mpc-connect) (jao-minibuffer-add-msg-variable 'jao-mpc-minibuffer-str 1)) (provide 'jao-mpc) ;;; jao-mpc.el ends here