summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bongo.el263
-rw-r--r--subed-conf.el55
-rw-r--r--text-conf.el2
3 files changed, 318 insertions, 2 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))
diff --git a/subed-conf.el b/subed-conf.el
new file mode 100644
index 0000000..f9404ad
--- /dev/null
+++ b/subed-conf.el
@@ -0,0 +1,55 @@
+;;; subed-conf.el --- Configurations of Subed -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2021 Durand
+
+;; Author: 李俊緯 <mmemmew@gmail.com>
+;; Keywords: convenience, multimedia, data
+
+;; 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 <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; My configurations of the superb package subed:
+;; https://github.com/sachac/subed
+
+;;; Code:
+
+(use-package "subed/subed" 'subed)
+
+;;; Use a shorter name for the socket
+
+(setq subed-mpv-socket-dir "/tmp/")
+
+;; Make some modifications so that mpv works
+
+(defun durand-subed-mpv--socket ()
+ "Path to mpv's RPC socket for a particular buffer.
+See also `subed-mpv-socket-dir'."
+ (unless (file-exists-p subed-mpv-socket-dir)
+ (condition-case err
+ (make-directory subed-mpv-socket-dir :create-parents)
+ (file-error
+ (error "%s" (mapconcat #'identity (cdr err) ": ")))))
+ (concat (file-name-as-directory subed-mpv-socket-dir)
+ (truncate-string-to-width
+ (format "%s%s"
+ (let ((filename (file-name-sans-extension (subed--buffer-file-name))))
+ (substring (substring filename 0 (min (length filename) 20))))
+ (buffer-hash))
+ 15)))
+
+(advice-add #'subed-mpv--socket :override #'durand-subed-mpv--socket)
+
+(provide 'subed-conf)
+;;; subed-conf.el ends here
diff --git a/text-conf.el b/text-conf.el
index ad0d78f..5bf2218 100644
--- a/text-conf.el
+++ b/text-conf.el
@@ -43,8 +43,6 @@ exactly the current ilne."
(goto-char (line-end-position))
(newline)
(while (< index floor-length)
- (message "index: %s" index)
- (message "floor: %s" floor-length)
(insert heading)
(setq index (1+ index)))
(insert remainder-string))))