aboutsummaryrefslogtreecommitdiff
path: root/emacs/.emacs.d/lisp/my/my-emms.el
diff options
context:
space:
mode:
Diffstat (limited to 'emacs/.emacs.d/lisp/my/my-emms.el')
-rw-r--r--emacs/.emacs.d/lisp/my/my-emms.el249
1 files changed, 228 insertions, 21 deletions
diff --git a/emacs/.emacs.d/lisp/my/my-emms.el b/emacs/.emacs.d/lisp/my/my-emms.el
index 803ac0a..e8be5ee 100644
--- a/emacs/.emacs.d/lisp/my/my-emms.el
+++ b/emacs/.emacs.d/lisp/my/my-emms.el
@@ -300,10 +300,23 @@ filter extensions from filter-exts."
(defvar my-emms-i3bar-file (locate-user-emacs-file "emms-i3bar")
"File to write current playing to which i3bar reads")
(defun my-emms-get-display-name (track)
+ "Return the display name of a track.
+
+The display name is either the info-title, or the display name of
+the filename."
(or (alist-get 'info-title track)
(when-let ((name
(alist-get 'name track)))
- (replace-regexp-in-string "^\\(.*/\\)\\(.*/.*/.*\\)" "\\2" name))))
+ (my-emms-get-display-name-1 name))))
+
+(defun my-emms-get-display-name-1 (name)
+ "Return the display name of a filename NAME.
+
+The display name is the last three components of the filename,
+assuming the filesystem hierarchy is arranged in
+artist/album/track."
+ (replace-regexp-in-string "^\\(.*/\\)\\(.*/.*/.*\\)" "\\2" name))
+
(defun my-emms-output-current-track-to-i3bar-file ()
(let ((current-track
(my-emms-get-display-name (emms-playlist-current-selected-track))))
@@ -361,17 +374,20 @@ filter extensions from filter-exts."
my-emms-favourites-playlist)))
;;; random album in emms
-(defun my-my-emms-current-album-name ()
+(defun my-emms-current-album-name ()
(file-name-directory (my-emms-get-current-track-name)))
+(defun my-emms-playlist-album-name-at-point ()
+ (file-name-directory (emms-track-get (emms-playlist-track-at) 'name)))
+
(defun my-emms-next-track-or-random-album ()
(interactive)
- (let ((current-album (my-my-emms-current-album-name)))
+ (let ((current-album (my-emms-current-album-name)))
(when emms-player-playing-p (emms-stop))
(emms-playlist-current-select-next)
- (if (string-equal (my-my-emms-current-album-name) current-album)
+ (if (string-equal (my-emms-current-album-name) current-album)
(emms-start)
- (my-emms-random-album nil))))
+ (my-emms-playlist-random-album))))
(defvar-local my-emms-albums-cache (vector))
@@ -402,20 +418,145 @@ under /zzz-seren/."
(elt my-emms-albums-cache (random (length my-emms-albums-cache)))))
album))
-(defun my-emms-random-album (update-album)
- (interactive "P")
+(defun my-emms-playlist-random-album ()
+ (interactive)
(with-current-emms-playlist
- (when (or update-album (length= my-emms-albums-cache 0))
- (my-emms-save-albums-cache))
- (when emms-player-playing-p (emms-stop))
- (let ((saved-position (point)))
- (goto-char (point-min))
- (if (search-forward
- (my-emms-get-random-album)
- nil t)
- (emms-playlist-mode-play-current-track)
- (goto-char saved-position)
- (error "Cannot play random album")))))
+ (goto-line (1+ (random (count-lines (point-min) (point-max)))))
+ (let ((album-name (my-emms-playlist-album-name-at-point)))
+ (goto-char (point-min))
+ (search-forward album-name)
+ (beginning-of-line)
+ (emms-playlist-mode-play-current-track))))
+
+(defvar my-emms-playlist-group-length 20
+ "Length of a track group in an album.")
+
+(defvar my-emms-playlist-tail-group-length 10
+ "Min length of a tail track group in an album.")
+
+(defun my-emms-playlist-group-bounds ()
+ "Return (GROUP-START . GROUP-END) of the group the current track belongs to."
+ (save-excursion
+ (let* ((album-name (my-emms-playlist-album-name-at-point))
+ (current-ln (line-number-at-pos))
+ (start-ln (progn (goto-char (point-min))
+ (re-search-forward (concat "^" (regexp-quote album-name)))
+ (line-number-at-pos)))
+ (end-ln (progn (goto-char (point-max))
+ (re-search-backward (concat "^" (regexp-quote album-name)))
+ (1+ (line-number-at-pos))))
+ ;; How many tracks have been from the start of the album
+ ;; (exclusive)
+ (past (- current-ln start-ln))
+ ;; ;; How many tracks to go (inclusive)
+ ;; (remain (- end-ln current-ln))
+ (idx (/ past my-emms-playlist-group-length))
+ (maybe-group-start (+ start-ln (* idx my-emms-playlist-group-length)))
+ (group-start
+ (if (< (- end-ln maybe-group-start) my-emms-playlist-tail-group-length)
+ ;; Too close to the end of the album
+ (max start-ln (- maybe-group-start my-emms-playlist-group-length))
+ maybe-group-start))
+ (maybe-group-end (+ group-start my-emms-playlist-group-length))
+ (group-end
+ (if (<= (- end-ln maybe-group-end) my-emms-playlist-tail-group-length)
+ end-ln
+ (min end-ln maybe-group-end))))
+ (cons group-start group-end))))
+
+(defvar-local my-emms-playlist-group-start-overlay nil)
+(defvar-local my-emms-playlist-group-end-overlay nil)
+
+(defun my-emms-playlist-mark-bounds (group-end)
+ "Mark bounds of the current track group.
+
+An up arrow at the first played in the current group, and a down
+arrow at the end of the track group."
+ (when my-emms-playlist-group-start-overlay
+ (delete-overlay my-emms-playlist-group-start-overlay))
+ (when my-emms-playlist-group-start-overlay
+ (delete-overlay my-emms-playlist-group-end-overlay))
+ (setq my-emms-playlist-group-start-overlay (make-overlay (point) (point)))
+ (overlay-put
+ my-emms-playlist-group-start-overlay
+ 'before-string (propertize
+ "x" 'display
+ `(left-fringe up-arrow emms-playlist-selected-face)))
+ (save-excursion
+ (goto-line (1- group-end))
+ (setq my-emms-playlist-group-end-overlay (make-overlay (point) (point)))
+ (overlay-put
+ my-emms-playlist-group-end-overlay
+ 'before-string (propertize
+ "x" 'display
+ `(left-fringe down-arrow emms-playlist-selected-face)))))
+
+(defun my-emms-mode-line-playlist-current ()
+ "Format the currently playing song.
+
+Override `emms-mode-line-playlist-current' to incorporate wide chars."
+ (let ((track-desc (my-emms-get-display-name-1
+ (emms-track-description
+ (emms-playlist-current-selected-track)))))
+ (format emms-mode-line-format
+ (if (< (string-width track-desc) emms-mode-line-length-limit)
+ track-desc
+ (concat
+ (seq-subseq
+ track-desc 0
+ (- (length track-desc)
+ (- (string-width track-desc) emms-mode-line-length-limit)))
+ "...")))))
+
+
+;; (defun my-emms-playing-time-mode-line ()
+;; "Add playing time to the mode line.
+
+;; Override `emms-playing-time-mode-line': prepend instead of append."
+;; (or global-mode-string (setq global-mode-string '("")))
+;; (unless (member 'emms-playing-time-string
+;; global-mode-string)
+;; (setq global-mode-string
+;; (append '(emms-playing-time-string) global-mode-string))))
+
+
+(defun my-emms-playlist-random-group ()
+ (interactive)
+ (with-current-emms-playlist
+ (let ((random-line (1+ (random (count-lines (point-min) (point-max))))))
+ (goto-line random-line)
+ (pcase-let ((`(,group-start . ,group-end) (my-emms-playlist-group-bounds)))
+ (message "my-emms-playlist-random-group: (%d, %d)" random-line group-start)
+ (goto-line group-start)
+ (my-emms-playlist-mark-bounds group-end)
+ (emms-playlist-mode-play-current-track)))))
+
+;;; TODO: mark bounds if and only if the currently played is out of
+;;; the existing overlay.
+(defun my-emms-playlist-maybe-mark-bounds ()
+ "Used as an `emms-player-started-hook'.
+
+If the last command is `emms-playlist-mode-play-smart' i.e. the
+user manually chose the track to play, and if
+`emms-player-next-function' is
+`my-emms-next-track-or-random-group', then mark boundaries since
+it would not have been marked otherwise."
+ (when (and (eq last-command 'emms-playlist-mode-play-smart)
+ (eq emms-player-next-function 'my-emms-next-track-or-random-group))
+ (with-current-emms-playlist
+ (pcase-let ((`(_ . ,group-end) (my-emms-playlist-group-bounds)))
+ (my-emms-playlist-mark-bounds group-end)))))
+
+(defun my-emms-next-track-or-random-group ()
+ (interactive)
+ (with-current-buffer emms-playlist-buffer
+ (emms-playlist-mode-center-current)
+ (pcase-let ((`(,group-start . ,group-end) (my-emms-playlist-group-bounds)))
+ (when emms-player-playing-p (emms-stop))
+ (if (>= (1+ (line-number-at-pos)) group-end)
+ (my-emms-playlist-random-group)
+ (emms-playlist-current-select-next)
+ (emms-start)))))
;;; override the minor mode
;;;###autoload
@@ -473,13 +614,79 @@ Hex-encoded characters in URLs are replaced by the decoded
character."
(let ((type (emms-track-type track)))
(cond ((emms-track-get track 'description)
- (emms-track-get track 'description))
- ((eq 'file type)
- (emms-track-name track))
+ (emms-track-get track 'description))
+ ((eq 'file type)
+ (emms-track-name track))
((eq 'url type)
(emms-format-url-track-name (emms-track-name track)))
(t (concat (symbol-name type)
": " (emms-track-name track))))))
+(defvar my-emms-score-delta 1)
+
+(defun my-emms-score-up-playing ()
+ "Increase score by `my-emms-score-delta', then reset the score delta to 1."
+ (emms-score-change-score
+ my-emms-score-delta
+ (my-emms-get-display-name-1 (emms-score-current-selected-track-filename)))
+ (setq my-emms-score-delta 1))
+
+(defun my-emms-score-show-playing ()
+ "Show score for current playing track in minibuf.
+
+Override `emms-score-show-playing' - using last three components in the name..."
+ (interactive)
+ (message "track/tolerance score: %d/%d"
+ (emms-score-get-score (my-emms-get-display-name-1
+ (emms-score-current-selected-track-filename)))
+ emms-score-min-score))
+
+(defun my-emms-score-up-chosen-bonus ()
+ "Bonus score up if the track is started intentionally.
+
+If the last command is `emms-playlist-mode-play-smart', then set
+`my-emms-score-delta' to 2."
+ (if (not (eq last-command 'emms-playlist-mode-play-smart))
+ (setq my-emms-score-delta 1)
+ (setq my-emms-score-delta 2)
+ (setq last-command nil))
+ )
+
+(defun my-emms-wrapped ()
+ "Print top 10 scored tracks."
+ (interactive)
+ (let (keys)
+ (maphash (lambda (k _) (push k keys)) emms-score-hash)
+ (sort keys (lambda (k1 k2)
+ (> (cl-second (gethash k1 emms-score-hash))
+ (cl-second (gethash k2 emms-score-hash)))))
+ (message "Top 10: %s" (string-join (take 10 keys) "\n"))))
+
+(defun my-emms-maybe-get-duration-for-current-track ()
+ "Get duration for the current track.
+
+Can be used as a `emms-player-started-hook'"
+ (unless (emms-track-get (emms-playlist-current-selected-track)
+ 'info-playing-time)
+ (my-emms-info-ffprobe (emms-playlist-current-selected-track))))
+
+(defun my-emms-info-ffprobe (track)
+ "Use ffprobe for urls to get duration.
+
+Call
+
+ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1
+
+on the url"
+ (when (eq (emms-track-type track) 'url)
+ (with-temp-buffer
+ (call-process "ffprobe" nil t nil "-v" "error" "-show_entries"
+ "format=duration" "-of" "default=noprint_wrappers=1:nokey=1"
+ (emms-track-name track))
+ (let ((duration (string-trim (buffer-string))))
+ (when (string-match-p "[0-9.]+" duration)
+ (emms-track-set track 'info-playing-time
+ (floor (string-to-number duration))))))))
+
(provide 'my-emms)
;;; my-emms.el ends here