diff options
author | Sacha Chua <sacha@sachachua.com> | 2022-11-10 08:04:31 -0500 |
---|---|---|
committer | Sacha Chua <sacha@sachachua.com> | 2022-11-10 08:04:31 -0500 |
commit | f16a68d2c43a38ac64de5a38ddcd87db09e737bf (patch) | |
tree | 747937a77e1e57373909ab7f36b3bc5a57f130f8 | |
parent | d6b6ce5ec0e73b22fe2da1ac8b9685419e1ad17e (diff) | |
download | emacsconf-el-f16a68d2c43a38ac64de5a38ddcd87db09e737bf.tar.xz emacsconf-el-f16a68d2c43a38ac64de5a38ddcd87db09e737bf.zip |
hyperlist, reload
-rw-r--r-- | emacsconf-publish.el | 106 | ||||
-rw-r--r-- | emacsconf-stream.el | 93 | ||||
-rw-r--r-- | emacsconf.el | 113 |
3 files changed, 236 insertions, 76 deletions
diff --git a/emacsconf-publish.el b/emacsconf-publish.el index a3c0323..72683bc 100644 --- a/emacsconf-publish.el +++ b/emacsconf-publish.el @@ -151,7 +151,31 @@ talk "<div class=\"vid\">${video-html}<div>${extra}</div>${resources}${chapter-list}</div>")))) +(defun emacsconf-publish-format-res-talks (info) + (mapconcat + (lambda (o) + (concat + "<tr>" + (format + "<td><a name=\"%s\"></a>" + (plist-get o :slug)) + (plist-get o :qa-link) + "</td>" + "<td>" (if (plist-get o :pad-url) + (format "<a href=\"%s\" target=\"_blank\" rel=\"noreferrer\">Open pad</a>" (plist-get o :pad-url)) + "") + "</td>" + "<td>" (format "<a href=\"%s\" target=\"_blank\" rel=\"noreferrer\">Open chat</a>" (plist-get o :webchat-url)) + "" + "</td>" + "<td>" (format-time-string "%-l:%M" (plist-get o :start-time) emacsconf-timezone) "</td>" + "<td>" (or (plist-get o :slug) "") "</td>" + "<td>" (or (plist-get o :title) "") "</td>" + "</tr>")) + info + "\n")) (defun emacsconf-publish-res-index () + "Publish BBB room URLs and pad links for volunteer convenience." (interactive) (let ((emacsconf-use-absolute-url t) (emacsconf-base-url "") @@ -166,43 +190,42 @@ o) o))) (emacsconf-prepare-for-display (emacsconf-get-talk-info))))) - (mapc (lambda (track) - (let ((track-talks (seq-filter (lambda (o) (string= (plist-get o :track) - (plist-get track :name))) - info))) - (with-temp-file (expand-file-name (format "index-%s.html" (plist-get track :id)) emacsconf-res-dir) - (insert - "<html><head><link rel=\"stylesheet\" href=\"style.css\"></head><body> + (mapc + (lambda (track) + (let* + ((track-talks (seq-filter (lambda (o) (string= (plist-get o :track) + (plist-get track :name))) + info)) + (result + (concat + "<html><head><link rel=\"stylesheet\" href=\"style.css\"></head><body> <div>" - (with-temp-buffer - (svg-print (emacsconf-schedule-svg 800 300 info)) - (buffer-string)) - "</div><table>" - (mapconcat - (lambda (o) - (concat - "<tr>" - (format - "<td><a name=\"%s\"></a>" - (plist-get o :slug)) - (plist-get o :qa-link) - "</td>" - "<td>" (if (plist-get o :pad-url) - (format "<a href=\"%s\" target=\"_blank\" rel=\"noreferrer\">Open pad</a>" (plist-get o :pad-url)) - "") - "</td>" - "<td>" (format "<a href=\"%s\" target=\"_blank\" rel=\"noreferrer\">Open chat</a>" (plist-get o :webchat-url)) - "" - "</td>" - "<td>" (format-time-string "%-l:%M" (plist-get o :start-time) emacsconf-timezone) "</td>" - "<td>" (or (plist-get o :slug) "") "</td>" - "<td>" (or (plist-get o :title) "") "</td>" - "</tr>")) - (seq-filter (lambda (talk) (string= (plist-get talk :track) - (plist-get track :name))) info) - "\n") - "</table></body></html>")))) - emacsconf-tracks))) + (with-temp-buffer + (svg-print (emacsconf-schedule-svg 800 300 info)) + (buffer-string)) + "</div><h1>" + (plist-get track :name) + "</h1><table>" + "<tr><th colspan=\"6\" style=\"text-align: left\">Saturday</th></tr>" + (emacsconf-publish-format-res-talks + (emacsconf-prepare-for-display + (emacsconf-filter-talks-by-time + (format "2022-12-03T08:00:00%s" emacsconf-timezone-offset) + (format "2022-12-03T18:00:00%s" emacsconf-timezone-offset) + track-talks))) + "<tr><th colspan=\"6\" style=\"text-align: left\">Sunday</th></tr>" + (emacsconf-publish-format-res-talks + (emacsconf-prepare-for-display + (emacsconf-filter-talks-by-time + (format "2022-12-04T08:00:00%s" emacsconf-timezone-offset) + (format "2022-12-04T18:00:00%s" emacsconf-timezone-offset) + track-talks))) + "</table></body></html>"))) + (with-temp-file (expand-file-name (format "index-%s.html" (plist-get track :id)) emacsconf-res-dir) + (insert result)) + (with-temp-file (expand-file-name (format "index-%s.html" (plist-get track :id)) emacsconf-backstage-dir) + (insert result)))) + emacsconf-tracks))) (defun emacsconf-index-card-video (video-id video-file talk extensions &optional backstage) (let* ((video-base (and video-file (file-name-base video-file))) @@ -875,15 +898,6 @@ Entries are sorted chronologically, with different tracks interleaved." (defun emacsconf-sum (field talks) (apply '+ (seq-map (lambda (talk) (string-to-number (or (plist-get talk field) "0"))) talks))) -(defun emacsconf-publish-backstage-add-to-todo-hook () - (interactive) - (with-current-buffer (find-file-noselect emacsconf-org-file) - (add-hook 'org-after-todo-state-change-hook #'emacsconf-publish-backstage-org-after-todo-state-change nil t))) -(defun emacsconf-publish-backstage-remove-from-todo-hook () - (interactive) - (with-current-buffer (find-file-noselect emacsconf-org-file) - (remove-hook 'org-after-todo-state-change-hook #'emacsconf-publish-backstage-org-after-todo-state-change t))) - (defun emacsconf-publish-backstage-org-on-state-change (talk) (save-window-excursion (emacsconf-with-talk-heading talk @@ -906,7 +920,7 @@ Entries are sorted chronologically, with different tracks interleaved." (insert "<html><head><meta charset=\"UTF-8\"><link rel=\"stylesheet\" href=\"/style.css\" /></head><body>" "<p>Schedule by status: (gray: waiting, light yellow: processing, yellow: to assign, light green: captioning, green: captioned and ready)<br />Updated by conf.org and the wiki repository</br />" - "<img src=\"https://emacsconf.org/2022/organizers-notebook/schedule.svg\" /></p>" + "<img src=\"schedule.svg\" /></p>" (format "<p>Waiting for %d talks (~%d minutes) out of %d total</p>" (length (assoc-default "WAITING_FOR_PREREC" by-status)) (emacsconf-sum :time (assoc-default "WAITING_FOR_PREREC" by-status)) diff --git a/emacsconf-stream.el b/emacsconf-stream.el index 9e93f20..705d0da 100644 --- a/emacsconf-stream.el +++ b/emacsconf-stream.el @@ -67,16 +67,22 @@ Files should be in YEAR/video-slug--main.webm and video-slug--main.vtt.") (defun emacsconf-stream-svg-set-text (dom id text) "Update DOM to set the tspan in the element with ID to TEXT. If the element doesn't have a tspan child, use the element itself." - (setq text (svg--encode-text text)) - (let ((node (or (dom-child-by-tag - (car (dom-by-id dom id)) - 'tspan) - (dom-by-id dom id)))) - (cond - ((null node)) ; skip - ((= (length node) 2) - (nconc node (list text))) - (t (setf (elt node 2) text))))) + (if (or (null text) (string= text "")) + (let ((node (dom-by-id dom id))) + (when node + (dom-set-attribute node 'style "visibility: hidden") + (dom-set-attribute (dom-child-by-tag node 'tspan) 'style "fill: none; stroke: none"))) + (setq text (svg--encode-text text)) + (let ((node (or (dom-child-by-tag + (car (dom-by-id dom id)) + 'tspan) + (dom-by-id dom id)))) + (cond + ((null node) + (error "Could not find node %s" id)) ; skip + ((= (length node) 2) + (nconc node (list text))) + (t (setf (elt node 2) text)))))) (defun emacsconf-stream-add-talk-props (talk) "Create an overlay for TALK. @@ -248,11 +254,16 @@ This uses the BBB room if available, or the IRC channel if not." (unless (file-directory-p emacsconf-stream-overlay-dir) (make-directory emacsconf-stream-overlay-dir)) (mapc (lambda (talk) - (emacsconf-stream-write-talk-overlay-svgs - talk - (expand-file-name (concat (plist-get talk :slug) "-video.svg") emacsconf-stream-overlay-dir) - (expand-file-name (concat (plist-get talk :slug) "-other.svg") emacsconf-stream-overlay-dir))) - info)) + (unless (file-exists-p (expand-file-name (concat (plist-get talk :slug) "-video.png") emacsconf-stream-overlay-dir)) + (emacsconf-stream-write-talk-overlay-svgs + talk + (expand-file-name (concat (plist-get talk :slug) "-video.svg") emacsconf-stream-overlay-dir) + (expand-file-name (concat (plist-get talk :slug) "-other.svg") emacsconf-stream-overlay-dir)))) + info) + (emacsconf-stream-write-talk-overlay-svgs + nil + (expand-file-name "blank-video.svg" emacsconf-stream-overlay-dir) + (expand-file-name "blank-other.svg" emacsconf-stream-overlay-dir))) (defun emacsconf-stream-display-talk-info (talk) (interactive (list (emacsconf-complete-talk-info))) @@ -314,6 +325,57 @@ This uses the BBB room if available, or the IRC channel if not." (set-frame-size nil 1280 720 t) (mapc #'emacsconf-stream-generate-title-page info))) +(defun emacsconf-stream-generate-in-between-pages (&optional info) + (interactive) + (setq info (emacsconf-prepare-for-display (emacsconf-filter-talks (or info (emacsconf-get-talk-info))))) + (let* ((by-track (seq-group-by (lambda (o) (plist-get o :track)) info)) + (dir (expand-file-name "in-between" emacsconf-stream-asset-dir)) + (template (expand-file-name "template.svg" dir))) + (mapc (lambda (track) + (let (prev) + (mapc (lambda (talk) + (let ((dom (xml-parse-file template))) + (mapc (lambda (entry) + (let ((prefix (car entry))) + (emacsconf-stream-svg-set-text dom (concat prefix "title") + (plist-get (cdr entry) :title)) + (emacsconf-stream-svg-set-text dom (concat prefix "speakers") + (plist-get (cdr entry) :speakers-with-pronouns)) + (emacsconf-stream-svg-set-text dom (concat prefix "url") + (and (cdr entry) (concat emacsconf-base-url (plist-get (cdr entry) :url)))) + (emacsconf-stream-svg-set-text + dom + (concat prefix "qa") + (pcase (plist-get (cdr entry) :q-and-a) + ("live" "Live Q&A after talk") + ("IRC" "IRC Q&A after talk") + (_ ""))))) + (list (cons "previous-" prev) + (cons "current-" talk))) + (with-temp-file (expand-file-name (concat (plist-get talk :slug) ".svg") dir) + (dom-print dom)) + (unless (file-exists-p (expand-file-name (concat (plist-get talk :slug) ".png") + dir)) + (shell-command + (concat "inkscape --export-type=png --export-dpi=300 --export-background-opacity=0 " + (shell-quote-argument (expand-file-name (concat (plist-get talk :slug) ".svg") + dir)))))) + (setq prev talk)) + (cdr track))) + (let ((default-directory dir)) + (shell-command + (concat + "convert " + (mapconcat (lambda (talk) (shell-quote-argument + (concat (plist-get talk :slug) ".png"))) + (cdr track) + " ") + " " + (plist-get (emacsconf-get-track (car track)) :id) + "-in-between.pdf" + )))) + by-track))) + (defun emacsconf-stream-generate-test-subtitles (&optional info) (interactive) (setq info (emacsconf-filter-talks (or info (emacsconf-get-talk-info)))) @@ -364,6 +426,7 @@ This uses the BBB room if available, or the IRC channel if not." (message "Done %s" talk) (undo-boundary)))) + (defun emacsconf-stream-schedule-timers (&optional info) (interactive) (setq info (emacsconf-filter-talks (or info (emacsconf-get-talk-info)))) diff --git a/emacsconf.el b/emacsconf.el index 8e4c4e1..2f5b6e2 100644 --- a/emacsconf.el +++ b/emacsconf.el @@ -134,7 +134,11 @@ (mapc (lambda (file) (copy-file file (expand-file-name (file-name-nondirectory file) emacsconf-backstage-dir) - t)) + t) + (when emacsconf-cache-dir + (copy-file file (expand-file-name (file-name-nondirectory file) + emacsconf-cache-dir) + t))) (or (dired-get-marked-files) (list (buffer-file-name))))) @@ -927,10 +931,12 @@ (emacsconf-backstage-password . emacsconf_backstage_password)))))) ;; (emacsconf-ansible-load-vars (expand-file-name "prod-vars.yml" emacsconf-ansible-directory)) ;;; Tracks -(defvar emacsconf-tracks '((:name "General" :color "peachpuff" :id "gen" :channel "emacsconf-gen" - :tramp "/ssh:emacsconf-gen@res.emacsconf.org#46668:") - (:name "Development" :color "skyblue" :id "dev" :channel "emacsconf-dev" - :tramp "/ssh:emacsconf-dev@res.emacsconf.org#46668:"))) +(defvar emacsconf-tracks + '( + (:name "General" :color "peachpuff" :id "gen" :channel "emacsconf-gen" + :tramp "/ssh:emacsconf-gen@res.emacsconf.org#46668:") + (:name "Development" :color "skyblue" :id "dev" :channel "emacsconf-dev" + :tramp "/ssh:emacsconf-dev@res.emacsconf.org#46668:"))) (defun emacsconf-get-track (name) (when (listp name) (setq name (plist-get name :track))) @@ -985,8 +991,9 @@ (when (stringp start-time) (setq start-time (date-to-time start-time))) (when (stringp end-time) (setq end-time (date-to-time end-time))) (seq-filter (lambda (o) - (and (not (time-less-p (plist-get o :start-time) end-time)) - (not (time-less-p start-time (plist-get o :end-time))))) + (and (plist-get o :start-time) + (time-less-p (plist-get o :start-time) end-time) + (time-less-p start-time (plist-get o :end-time)))) info)) (defun emacsconf-get-shift (time) @@ -1070,7 +1077,7 @@ Filter by TRACK if given. Use INFO as the list of talks." (defun emacsconf-reflow () "Help reflow text files." (interactive) - (let (input last-input) + (let (input last-input (case-fold-search t)) (while (not (string= "" (setq input (read-string "Word: ")))) (when (string= input "!") (delete-backward-char 1) @@ -1114,9 +1121,9 @@ Filter by TRACK if given. Use INFO as the list of talks." #'emacsconf-org-after-todo-state-change t))) (defvar emacsconf-todo-hooks - '(emacsconf-stream-play-talk-on-change ;; play the talk - emacsconf-stream-open-qa-windows-on-change - emacsconf-erc-announce-on-change ;; announce via ERC + '((emacsconf-stream-play-talk-on-change "gen") ;; play the talk - dev will be managed separately + (emacsconf-stream-open-qa-windows-on-change "gen") + ;; emacsconf-erc-announce-on-change ;; announce via ERC emacsconf-publish-media-files-on-change emacsconf-publish-bbb-redirect emacsconf-publish-backstage-org-on-state-change ;; update the backstage index @@ -1126,9 +1133,18 @@ Filter by TRACK if given. Use INFO as the list of talks." They will be called with TALK.") (defun emacsconf-org-after-todo-state-change () - "Run all the hooks in `emacsconf-todo-hooks'." - (let ((talk (emacsconf-get-talk-info-for-subtree))) - (run-hook-with-args 'emacsconf-todo-hooks talk))) + "Run all the hooks in `emacsconf-todo-hooks'. +If an `emacsconf-todo-hooks' entry is a list, run it only for the +tracks with the ID in the cdr of that list." + (let* ((talk (emacsconf-get-talk-info-for-subtree)) + (track (emacsconf-get-track (plist-get talk :track)))) + (mapc + (lambda (hook-entry) + (cond + ((symbolp hook-entry) (funcall hook-entry talk)) + ((member (plist-get track :id) (cdr hook-entry)) + (funcall (car hook-entry) talk)))) + emacsconf-todo-hooks))) (defun emacsconf-broadcast (message) (interactive "MMessage: ") @@ -1149,10 +1165,77 @@ They will be called with TALK.") (org-agenda-list nil emacsconf-date 2))) (defun emacsconf-update-talk-status (slug from-states to-state) - (interactive (list (emacsconf-complete-talk) (read-string "From: ") (read-string "To: "))) + (interactive (list (emacsconf-complete-talk) "." (completing-read "To: " (mapcar 'car emacsconf-status-types)))) (emacsconf-with-talk-heading slug (when (string-match from-states (org-entry-get (point) "TODO")) (org-todo to-state)))) +;; copied from org-ascii--indent-string +(defun emacsconf-indent-string (s width) + "Indent S by WIDTH spaces." + (replace-regexp-in-string "\\(^\\)[ \t]*\\S-" (make-string width ?\s) s nil nil 1)) + +(defun emacsconf-shift-hyperlist (params talks &optional do-insert) + "Use PARAMS to personalize the shift hyperlist." + (mapconcat (lambda (talk) + (emacsconf-talk-hyperlist (append (assoc-default (plist-get (emacsconf-get-track talk) :id) + params) + talk) + do-insert)) + talks)) + +(defun emacsconf-talk-hyperlist (talk &optional do-insert) + (interactive (list (emacsconf-complete-talk-info) t)) + (let ((result + (emacsconf-replace-plist-in-string + talk + (format "- %s %s %s %s\n%s" + (format-time-string "%H:%M" (plist-get talk :start-time) emacsconf-timezone) + (plist-get talk :track) + (plist-get talk :slug) + (plist-get talk :title) + (replace-regexp-in-string + "\\(^\\)[ \t]*\\S-" " " + (pcase (or (plist-get talk :q-and-a) "") + ((rx "live") + "- [ ] ${checkin}: Check ${speakers-with-pronouns} (${irc}) into BBB room sometime beforehand +- [ ] ${stream}: Display the in-between slide +- [ ] ${host}: Connect to the ${track} channel in Mumble and introduce the talk +- [ ] ${stream}: [[elisp:(emacsconf-update-talk-status \"${slug}\" \".\" \"PLAYING\")][Play the talk]] +- [ ] ${host}: Join the Q&A room and open the pad; optionally open IRC for ${channel} +- [ ] [? speaker missing?] ${host}: Let #emacsconf-org know so that we can text or call the speaker +- [ ] ${stream}: After the talk, [[elisp:(emacsconf-update-talk-status \"${slug}\" \".\" \"CLOSED_Q\")][open the Q&A window and the pad]], give the host the go-ahead via Mumble or #emacsconf-org +- [ ] ${host}: Start recording and read questions +- [ ] ${host}: [[elisp:(emacsconf-update-talk-status \"${slug}\" \".\" \"OPEN_Q\")][Decide when to open the Q&A]] and announce that people can join using the URL on the talk page +- [? Open Q&A is still going on and it's about five minutes before the next talk] + - [ ] ${host}: Let the speaker know about the time and that the Q&A can continue off-stream if people want to join +- [? Open Q&A is still going on and it's about two minutes before the next talk] + - [ ] ${host}: Announce that the Q&A will continue if people want to join the BBB room from the talk page, and the stream will now move to the next talk +- [? Q&A is done] + - [ ] ${host}: [[elisp:(emacsconf-update-talk-status \"${slug}\" \".\" \"TO_ARCHIVE\")][Mark talk as to archive]], close Q&A windows +- [ ] ${stream}: Close the Q&A windows and move on to the next talk +") + ((rx "irc") + "- [ ] ${stream}: Display the in-between slide +- [ ] ${host}: Connect to the ${track} channel in Mumble and introduce the talk +- [ ] ${stream}: [[elisp:(emacsconf-update-talk-status \"${slug}\" \".\" \"PLAYING\")][Start talk]] +- [ ] ${stream}: [[elisp:(emacsconf-update-talk-status \"${slug}\" \".\" \"OPEN_Q\")][Open the IRC channel (${channel}) and the pad]] +- [ ] ${stream}: When it's time for the next talk, close the Q&A windows and move on to the next talk +") + (_ + "- [ ] ${stream}: Display the in-between slide +- [ ] ${host}: Connect to the ${track} channel in Mumble and introduce the talk +- [ ] ${stream}: [[elisp:(emacsconf-update-talk-status \"${slug}\" \".\" \"PLAYING\")][Start talk]] +- [ ] ${stream}: [[elisp:(emacsconf-update-talk-status \"${slug}\" \".\" \"OPEN_Q\")][Open the IRC channel (${channel}) and the pad]] +- [ ] ${stream}: When it's time for the next talk, close the Q&A windows and move on to the next talk +")) + nil nil 1))))) + (if do-insert (insert result)) + result)) + +(defun emacsconf-reload () + "Reload the emacsconf-el modules." + (interactive) + (mapc #'load-library '("emacsconf" "emacsconf-erc" "emacsconf-publish" "emacsconf-stream" "emacsconf-pad"))) (provide 'emacsconf) ;;; emacsconf.el ends here |