diff options
| -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  | 
