summaryrefslogtreecommitdiff
path: root/bongo.el
diff options
context:
space:
mode:
authorJSDurand <mmemmew@gmail.com>2022-01-29 19:13:21 +0800
committerJSDurand <mmemmew@gmail.com>2022-01-29 19:13:42 +0800
commitd96af7fa0231ed697fc3dfbaa42a40a45a93cd3d (patch)
tree7dd802d86356c2784bdabbee540d89bd4cf1987c /bongo.el
parent9a665974c00bec7848e49b2e87a93e21f6cc4241 (diff)
Refactor: save something and tidy things up.
* bongo.el: Try to implement a mechanism to play subtitles along with the music, but it is not yet finished. * subed-conf.el: Make it work. * text-conf.el (insert-section-heading): Delete unnecessary debugging mechanisms.
Diffstat (limited to 'bongo.el')
-rw-r--r--bongo.el263
1 files changed, 263 insertions, 0 deletions
diff --git a/bongo.el b/bongo.el
index 7aa33ec..a0efde3 100644
--- a/bongo.el
+++ b/bongo.el
@@ -749,10 +749,273 @@ will do the right renaming."
(write-region nil nil file)))))
files)))
+;;; Play subtitles synchronously
+
+;; Sometimes I want to play the subtitles as the song plays.
+
+(defvar durand-bongo-sub-buffer-name "*Subtitles*"
+ "The name of the buffer for playing subtitles.")
+
+(defvar durand-bongo-sub-buffer nil
+ "The buffer for playing subtitles.")
+
+(defun durand-bongo-sub-stop-timer (player)
+ "Stop subtitle timer for the PLAYER."
+ (let ((timer (bongo-player-get player 'sub-timer)))
+ (cond
+ (timer
+ (cancel-timer timer)
+ (bongo-player-put player 'sub-timer nil)))))
+
+(defun durand-bongo-sub-tick (player)
+ "Tick when playing subtitles."
+ (if (or (null bongo-sub-buffer)
+ (not (bongo-player-running-p player))
+ (and (bongo-player-get player 'socket)
+ (not (equal (process-status (bongo-player-get player 'socket))
+ 'open))))
+ (durand-bongo-sub-stop-timer player)
+ (bongo--run-mpv-command player "sub-pos" "get_property" "time-pos")
+ (bongo-sub-redisplay)))
+
+(defun durand-bongo-sub-start-timer (player)
+ "Start ticking when playing subtitles."
+ (durand-bongo-sub-stop-timer player)
+ (let ((timer (run-with-timer bongo-mpv-initialization-period
+ (bongo-player-get
+ player 'time-update-delay-after-seek)
+ 'durand-bongo-sub-tick
+ player)))
+ (bongo-player-put player 'sub-timer timer)))
+
+(defun durand-bongo-sub-start-timer-maybe (&rest _args)
+ "Start the subtitle timer for the mpv backend."
+ (with-bongo-playlist-buffer
+ (cond
+ ((and bongo-player (eq (car bongo-player) 'mpv))
+ (durand-bongo-sub-start-timer bongo-player)))))
+
+;;;; My custom filter
+
+;; To handle subtitle events
+
+(defun durand-bongo--mpv-socket-filter (process output)
+ "Filter for socket connection with mpv.
+
+PROCESS is the socket which returned the OUTPUT."
+ (let ((player (process-get process 'bongo-player)))
+ (dolist (parsed-response (mapcar #'json-read-from-string
+ (split-string output "\n" t)))
+ ;; Events are treated differently from normal responses
+ (if (assoc 'event parsed-response)
+ (pcase (bongo-alist-get parsed-response 'event)
+ (`"pause" (progn
+ (bongo-player-put player 'paused t)
+ (bongo-player-paused/resumed player)))
+ (`"unpause" (progn
+ (bongo-player-put player 'paused nil)
+ (bongo-player-paused/resumed player))))
+ ;; Use request-id to identify the type of response
+ (pcase (bongo-alist-get parsed-response 'request_id)
+ (`"sub-pos" (progn
+ (message "data = %S" (bongo-alist-get parsed-response 'data))
+ ;; (durand-bongo-update-sub-time
+ ;; player
+ ;; (bongo-alist-get parsed-response 'data))
+ ))
+ (`"time-pos" (progn
+ (bongo-player-update-elapsed-time player
+ (bongo-alist-get parsed-response
+ 'data))
+ (bongo-player-times-changed player)))
+ (`"duration" (progn
+ (bongo-player-update-total-time player
+ (bongo-alist-get parsed-response
+ 'data))
+ (bongo-player-times-changed player)))
+ (`"metadata" (let* ((data (bongo-alist-get parsed-response 'data))
+ (album (bongo-alist-get data 'album))
+ (title (bongo-alist-get data 'title))
+ (genre (bongo-alist-get data 'genre)))
+ (bongo-player-put player 'metadata-fetched t)
+ (when album
+ (bongo-player-put player 'stream-name album))
+ (when title
+ (bongo-player-put player 'stream-part-title title))
+ (when genre
+ (bongo-player-put player 'stream-genre genre))
+ (when (or album title genre)
+ (bongo-player-metadata-changed player)))))))))
+
+(advice-add #'bongo--mpv-socket-filter :override
+ #'durand-bongo--mpv-socket-filter)
+
+;;;; Update subtime
+
+(defun durand-bongo-update-sub-time (player elapsed-time)
+ "Set PLAYER's `sub-time' property to ELAPSED-TIME,
+unless PLAYER's last seek happened less than N seconds ago, where N
+is the value of PLAYER's `time-update-delay-after-seek' property."
+ (let ((delay (bongo-player-get player 'time-update-delay-after-seek)))
+ (when (or (null delay) (zerop delay)
+ (let ((time (bongo-player-get player 'last-sub-time)))
+ (or (null time)
+ (time-less-p (seconds-to-time delay)
+ (subtract-time (current-time) time)))))
+ (bongo-player-put player 'sub-time elapsed-time))))
+
+;;;; Redisplay
+
+(defvar bongo-sub-redisplaying nil
+ "Non-nil in the dynamic scope of `bongo-sub-redisplay'.")
+
+(defun bongo-sub-redisplay ()
+ "Update the Bongo Subtitle buffer to reflect the current time."
+ (interactive)
+ (unless bongo-sub-redisplaying
+ (let ((bongo-sub-redisplaying t))
+ (let ((inhibit-read-only t))
+ (set-buffer bongo-sub-buffer)
+ (delete-region (point-min) (point-max))
+ (insert (bongo-sub-status-string (window-width)))))))
+
+;;;; Display string
+
+(defun durand-convert-sub-second (time sub)
+ "Convert TIME to add sub-seconds time given by SUB.
+TIME must be a list of two to four elements."
+ (cond
+ ((and (listp time)
+ (<= (length time) 4)
+ (<= 2 (length time))))
+ ((error "Wrong time: %S" time)))
+ ;; high
+ (let ((th (car time))
+ (tl (cadr time))
+ (tu (cond
+ ((>= (length time) 3) (nth 3 time))
+ (0)))
+ (tp (cond
+ ((>= (length time) 4) (nth 4 time))
+ (0)))
+ (sub-digit-n
+ (let ((n sub)
+ (count 0))
+ (while (> n 0)
+ (setq count (1+ count))
+ (setq n (/ n 10)))
+ count)))
+ (cond
+ ((<= sub-digit-n 6)
+ (setq tu (+ tu sub)))
+ ((<= sub-digit-n 12)
+ (setq tu (+ tu (/ sub (expt 10 6))))
+ (setq tp (+ tp (% sub (expt 10 6)))))
+ ((error "Wrong sub: %S" sub)))
+ (append
+ (list th tl)
+ (delq
+ nil
+ (mapcar
+ (lambda (ele)
+ (cond ((/= ele 0) ele)))
+ (list tu tp))))))
+
+(defun bongo-sub-status-string (width)
+ "Return the subtitle filled to WIDTH."
+ (cond
+ ((and (bongo-playing-p)
+ (bongo-player-get bongo-player 'sub-time)
+ (bongo-player-get bongo-player 'sub-file-buffer))
+ (let ((fb (bongo-player-get bongo-player 'sub-file-buffer))
+ (time (bongo-player-get bongo-player 'sub-time))
+ found start start-sub end end-sub)
+ (setq time
+ (parse-time-string
+ (format-seconds "%.2h:%.2m:%.2s" time)))
+ (with-current-buffer fb
+ (goto-char (point-min))
+ (save-match-data
+ (while (and
+ (not found)
+ (re-search-forward
+ (rx bol
+ (group
+ (1+ digit) ":"
+ (1+ digit) ":" (1+ digit)
+ (zero-or-one
+ "," (1+ digit)))
+ (zero-or-more " ")
+ "-->"
+ (zero-or-more " ")
+ (group
+ (1+ digit) ":"
+ (1+ digit) ":" (1+ digit)
+ (zero-or-one
+ "," (1+ digit)))
+ eol)
+ nil t))
+ (setq start (match-string 1))
+ (setq end (match-string 2))
+ (cond
+ ((string-match ",\\([[:digit:]]+\\)$" start)
+ (setq start-sub (match-string 1 start))
+ (setq
+ start
+ (durand-convert-sub-second
+ (let ((fake-time (parse-time-string
+ (replace-regexp-in-string
+ ",.*$" "" start))))
+ (encode-time
+ (append
+ (mapcar (lambda (ele) (or ele 0))
+ (durand-take 6 fake-time))
+ (nthcdr 6 fake-time))))
+ (string-to-number start-sub))))
+ ((setq start (parse-time-string start))))
+ (cond
+ ((string-match ",\\([[:digit:]]+\\)$" end)
+ (setq end-sub (match-string 1 end))
+ (setq
+ end
+ (durand-convert-sub-second
+ (let ((fake-time (parse-time-string
+ (replace-regexp-in-string
+ ",.*$" "" end))))
+ (encode-time
+ (append
+ (mapcar (lambda (ele) (or ele 0))
+ (durand-take 6 fake-time))
+ (nthcdr 6 fake-time))))
+ (string-to-number end-sub))))
+ ((setq end (parse-time-string end))))
+ (cond
+ ((and (time-less-p start time)
+ (time-less-p time end))
+ (setq found t))))
+ (cond
+ (found
+ (save-match-data
+ (save-excursion
+ (setq
+ end
+ (cond
+ ((re-search-forward (rx bol (zero-or-more space) eol) nil t)
+ (point))
+ ((point-max)))))
+ (buffer-substring-no-properties
+ (1+ (point)) end))))))))))
+
;; Normally we should do this, but I override the function by my
;; custom function, so I can just call this function there.
;; (advice-add #'bongo-seek :after #'durand-bongo-seek-start-timer-maybe)
+;;;; Subtitle command
+
+;; TODO
+
+;;; Volume package
+
(use-package "volume" 'volume
(define-key volume-mode-map [?s] #'volume-set))