diff options
Diffstat (limited to 'bongo.el')
-rw-r--r-- | bongo.el | 263 |
1 files changed, 263 insertions, 0 deletions
@@ -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)) |