From 808c2d605dc93f94bd79941e2fd530035a690abb Mon Sep 17 00:00:00 2001 From: Sacha Chua Date: Tue, 29 Nov 2022 14:13:41 -0500 Subject: hyperlists --- emacsconf-hyperlist.el | 181 ++++++++++++++++++++++++++++++ emacsconf-pad.el | 6 +- emacsconf-publish.el | 29 ++--- emacsconf-schedule.el | 3 +- emacsconf-stream.el | 291 +++++++++++++++++++++++++++++++------------------ emacsconf.el | 69 +++++++----- 6 files changed, 423 insertions(+), 156 deletions(-) create mode 100644 emacsconf-hyperlist.el diff --git a/emacsconf-hyperlist.el b/emacsconf-hyperlist.el new file mode 100644 index 0000000..d65979a --- /dev/null +++ b/emacsconf-hyperlist.el @@ -0,0 +1,181 @@ +;;; emacsconf-hyperlist.el --- step-by-step checklists -*- lexical-binding: t; -*- + +;; Copyright (C) 2022 Sacha Chua + +;; Author: Sacha Chua +;; Keywords: convenience + + +;; 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 . + +;;; Commentary: + +(defun emacsconf-hyperlist-format-talk-streamer (talk) + (setq talk (emacsconf-resolve-talk talk)) + (emacsconf-replace-plist-in-string + (append + (list :hhmm (format-time-string "%H:%M" (plist-get talk :start-time) emacsconf-timezone) + :intro-type (if (plist-get talk :recorded-intro) "recorded" "live") + :talk-type (if (plist-get talk :video-file) "recorded" "live") + :qa-type (or (plist-get talk :q-and-a) "none") + ) + talk) + (concat + "- ${hhmm} ${track-id} ${slug} (intro: ${intro-type}, talk: ${talk-type}, Q&A: ${qa-type}) [[${absolute-url}][talk page]]\n" + (emacsconf-surround " - " (plist-get talk :hyperlist-note) "\n" "") + " - [ ] [[elisp:(emacsconf-update-talk-status-with-hooks \"${slug}\" \".\" \"PLAYING\")][set talk playing]]\n" + ;; Intro + (cond + ((plist-get talk :recorded-intro) + " - [[elisp:(emacsconf-stream-play-intro \"${slug}\")][backup: play intro]]\n") + ((plist-get talk :video-file) ;; recorded talk, so intro comes from Mumble + " - [[elisp:(emacsconf-play-intro \"${slug}\")][backup: open in-between slide]]\n") + (t ;; live talk and intro, join BBB + " - [[elisp:(emacsconf-stream-bbb \"${slug}\")][backup: join BBB for live intro and talk]] + - [ ] adjust audio as needed\n")) + ;; Talk + (cond + ;; video should already have played + ((and (plist-get talk :recorded-intro) (plist-get talk :video-file)) + " - [[elisp:(emacsconf-stream-play-video \"${slug}\")][backup: play video]]\n") + ;; play video manually if intro was live + ((plist-get talk :video-file) + " - [ ] [[elisp:(emacsconf-stream-play-video \"${slug}\")][play video]] after the host finishes introducing it\n") + ;; recorded intro, live talk + ((plist-get talk :recorded-intro) + " - [ ] [[elisp:(emacsconf-stream-bbb \"${slug}\")][join BBB for live talk]] + - [ ] adjust audio as needed\n" + )) + (if (plist-get talk :video-file) + "" + ;; live talks will continue with Q&A + " - [ ] [[elisp:(emacsconf-update-talk-status-with-hooks \"${slug}\" \".\" \"CLOSED_Q\")][set talk closed q]] when the Q&A seems to be starting + - [[elisp:(emacsconf-stream-open-pad \"${slug}\")][backup: open pad]] + - [[elisp:(emacsconf-stream-join-chat \"${slug}\")][backup: join chat]] + - [ ] [[elisp:(emacsconf-update-talk-status-with-hooks \"${slug}\" \".\" \"OPEN_Q\")][set talk open q]] when the host gives the go-ahead + - [ ] [[elisp:(emacsconf-update-talk-status-with-hooks \"${slug}\" \".\" \"TO_ARCHIVE\")][set talk to archive]] when done +" + ) + ))) + +;; assumes the talk is not live +(defun emacsconf-hyperlist-format-qa-streamer (talk) + (setq talk (emacsconf-resolve-talk talk)) + (emacsconf-replace-plist-in-string + (append + (list :hhmm (format-time-string "%H:%M" (plist-get talk :qa-time) emacsconf-timezone) + :intro-type (if (plist-get talk :recorded-intro) "recorded" "live") + :talk-type (if (plist-get talk :video-file) "recorded" "live") + :qa-type (or (plist-get talk :q-and-a) "none") + ) + talk) + (concat + "- ${hhmm} ${track-id} ${slug} Q&A: ${q-and-a} [[${absolute-url}][talk page]] + - [ ] [[elisp:(emacsconf-update-talk-status-with-hooks \"${slug}\" \".\" \"CLOSED_Q\")][set talk closed q]] +" + (pcase (or (plist-get talk :q-and-a) "") + ((rx "live") + " - [[elisp:(emacsconf-stream-bbb \"${slug}\")][backup: join BBB]] + - [[elisp:(emacsconf-stream-open-pad \"${slug}\")][backup: open pad]] + - [ ] Check that streaming has started + - [ ] Give the host the go-ahead via Mumble or #emacsconf-org + - [ ] [[elisp:(emacsconf-update-talk-status-with-hooks \"${slug}\" \".\" \"OPEN_Q\")][set talk open q]] + - [ ] Confirm BBB redirect at ${bbb-redirect} goes to BBB room, let host know +") + ((rx "irc") + " - [[elisp:(emacsconf-stream-join-chat \"${slug}\")][backup: join chat]] + - [[elisp:(emacsconf-stream-open-pad \"${slug}\")][backup: open pad]] + - [ ] [[elisp:(emacsconf-update-talk-status-with-hooks \"${slug}\" \".\" \"OPEN_Q\")][set talk open q]] +") + ((rx "Mumble") + " + - [ ] Bring the speaker's Mumble login over to the ${channel} channel in Mumble. Confirm that Mumble is audible and adjust audio as needed: ssh emacsconf-${track-id}@res.emacsconf.org -p 46668 \"mum-vol 85%%\" (or mum-louder, mum-quieter) + - [ ] [[elisp:(emacsconf-update-talk-status-with-hooks \"${slug}\" \".\" \"OPEN_Q\")][set talk open q]] +") + ((rx "after") + " - [[elisp:(emacsconf-stream-join-chat \"${slug}\")][backup: join chat]] + - [[elisp:(emacsconf-stream-open-pad \"${slug}\")][backup: open pad]] + - [ ] [[elisp:(emacsconf-update-talk-status-with-hooks \"${slug}\" \".\" \"OPEN_Q\")][set talk open q]] +" + )) + " - [ ] [[elisp:(emacsconf-update-talk-status-with-hooks \"${slug}\" \".\" \"TO_ARCHIVE\")][set talk to archive]] +" + ))) + +(defun emacsconf-hyperlist-day-events (day &optional track info) + (let* ((talks + (emacsconf-prepare-for-display + (emacsconf-filter-talks-by-time + (concat day "T00:00:00" emacsconf-timezone-offset) + (concat day "T23:59:59" emacsconf-timezone-offset) + (if track + (emacsconf-filter-talks-by-track (or info (emacsconf-get-talk-info))) + (or info (emacsconf-get-talk-info))))))) + (sort + (apply #'append + (mapcar + (lambda (talk) + (if (plist-get talk :video-file) + (list + (cons (plist-get talk :start-time) + (emacsconf-hyperlist-format-talk-streamer talk)) + (cons (plist-get talk :qa-time) + (emacsconf-hyperlist-format-qa-streamer talk))) + (list + (cons (plist-get talk :start-time) + (emacsconf-hyperlist-format-talk-streamer talk))))) + talks)) + (lambda (a b) + (time-less-p (car a) (car b)))))) + +(defun emacsconf-hyperlist-format-day (day &optional track info) + (setq info (emacsconf-prepare-for-display + (if info (mapcar #'emacsconf-resolve-talk info) + (emacsconf-get-talk-info)))) + (when track (setq info (emacsconf-filter-talks-by-track track info))) + (when day (setq info (emacsconf-filter-talks-by-time + (concat day "T00:00:00" emacsconf-timezone-offset) + (concat day "T23:59:59" emacsconf-timezone-offset) + info))) + (let* ((events + (sort + (apply #'append + (mapcar + (lambda (talk) + (if (plist-get talk :video-file) + (list + (cons (plist-get talk :start-time) + (emacsconf-hyperlist-format-talk-streamer talk)) + (cons (plist-get talk :qa-time) + (emacsconf-hyperlist-format-qa-streamer talk))) + (list + (cons (plist-get talk :start-time) + (emacsconf-hyperlist-format-talk-streamer talk))))) + info)) + (lambda (a b) + (time-less-p (car a) (car b)))))) + (mapconcat #'cdr events ""))) + +(defun emacsconf-hyperlist-show-streamer-day (date &optional track info) + "Display the streamer hyperlist for DATE." + (interactive (list (org-read-date "Date: "))) + (pop-to-buffer (get-buffer-create "*hyperlist*")) + (erase-buffer) + (insert (emacsconf-hyperlist-format-day date track info)) + (goto-char (point-min)) + (org-mode)) + +;;; Code: +(provide 'emacsconf-hyperlist) +;;; emacsconf.el ends here diff --git a/emacsconf-pad.el b/emacsconf-pad.el index b798b5e..c0f84d5 100644 --- a/emacsconf-pad.el +++ b/emacsconf-pad.el @@ -456,8 +456,7 @@ ${bbb-checklist}") (setq info (or info (emacsconf-prepare-for-display (emacsconf-get-talk-info)))) (mapc (lambda (day) - (let ((pad-id (concat "checkin-" (downcase (format-time-string "%a" (plist-get (cadr day) :checkin-time))))) - ) + (let ((pad-id (concat "checkin-" (downcase (format-time-string "%a" (plist-get (cadr day) :checkin-time)))))) (emacsconf-pad-create-pad pad-id) (emacsconf-pad-set-html pad-id @@ -648,7 +647,6 @@ ${bbb-checklist}")
  • Backup link to pad: ${pad-url}
  • Backup link to #${channel}: ${webchat-url}
  • [ ] ${stream}: Update the task status (no visible changes): ${ssh-openq}
  • -
  • [ ] ${stream}: Confirm BBB redirect at ${bbb-redirect} goes to BBB room, let host know
  • [ ] ${host}: Announce that people can ask questions in the ${channel} IRC channel.
  • ") ((rx "Mumble") @@ -656,7 +654,7 @@ ${bbb-checklist}")
  • [ ] ${stream}: Bring the speaker's Mumble login over to the ${channel} channel in Mumble. Confirm that Mumble is audible and adjust audio as needed: ssh emacsconf-${track-id}@res.emacsconf.org -p 46668 \"mum-vol 85%%\" (or mum-louder, mum-quieter)
  • [ ] ${stream}: Mark the Q&A as closed: ${ssh-closedq} . This should display the QA slide (backup: ${ssh-track} and run firefox ${qa-slide-url} &)
  • [ ] ${stream}: Update the task status (no visible changes): ${ssh-openq}
  • -
  • [ ] ${stream}: Confirm BBB redirect at ${bbb-redirect} goes to BBB room, let host know
  • +
  • [ ] ${host}: Announce that people can ask questions in the pad or on the ${channel} IRC channel.
  • ") ((rx "after") diff --git a/emacsconf-publish.el b/emacsconf-publish.el index ed83cd6..17e0c1a 100644 --- a/emacsconf-publish.el +++ b/emacsconf-publish.el @@ -1630,25 +1630,21 @@ This video is available under the terms of the Creative Commons Attribution-Shar (interactive) (let* ((slug (org-entry-get (point) "VIDEO_SLUG")) (video-file (emacsconf-get-preferred-video slug)) - (wiki-captions-directory (expand-file-name "captions" (expand-file-name emacsconf-year emacsconf-directory))) - (new-captions-file (expand-file-name (concat slug "--main.vtt") wiki-captions-directory))) + (wiki-captions-directory (expand-file-name "captions" (expand-file-name emacsconf-year emacsconf-directory)))) (org-entry-put (point) "PUBLIC" "1") (when (file-exists-p video-file) (emacsconf-youtube-edit) - (emacsconf-toobnix-edit) - (emacsconf-cache-video-data-for-entry) - (emacsconf-update-talk) - (when (file-exists-p (expand-file-name (concat slug ".md") wiki-captions-directory)) - (with-current-buffer (find-file-noselect (file-exists (expand-file-name (concat slug ".md") wiki-captions-directory))) - (magit-stage-file (buffer-file-name)))) + (emacsconf-toobnix-edit) + (emacsconf-publish-update-talk (emacsconf-get-talk-info-for-subtree)) (mapc (lambda (suffix) (when (file-exists-p (expand-file-name (concat slug suffix) emacsconf-cache-dir)) - (copy-file (expand-file-name (concat slug suffix) emacsconf-cache-dir) - (expand-file-name (concat slug suffix) wiki-captions-directory)t) - (with-current-buffer (find-file-noselect (expand-file-name (concat slug suffix) wiki-captions-directory)) - (magit-stage-file (buffer-file-name))))) - '("--main.vtt" "--chapters.vtt" "--main_ja.vtt" "--main_fr.vtt")) + (let ((default-directory wiki-captions-directory)) + (copy-file (expand-file-name (concat slug suffix) emacsconf-cache-dir) + (expand-file-name (concat slug suffix) wiki-captions-directory) + t) + (call-process "git" nil nil nil "add" (concat slug suffix))))) + '("--main.vtt" "--chapters.vtt" "--main_ja.vtt" "--main_fr.vtt" "--main_es.vtt")) (magit-status-setup-buffer emacsconf-directory) (when (and emacsconf-public-media-directory slug (> (length (string-trim slug)) 0) ;; TODO: make this customizable @@ -1678,12 +1674,7 @@ This video is available under the terms of the Creative Commons Attribution-Shar `(progn ,@body (let ((default-directory emacsconf-directory)) - (if (featurep 'magit) - (progn - (magit-with-toplevel - (magit-stage-1 "-u" magit-buffer-diff-files)) - (magit-status-setup-buffer)) - (shell-command "git add -u")) + (shell-command "git add -u") ;; (when noninteractive ;; (call-process "git" nil nil nil "commit" "-m" (if (stringp (car body)) ;; (car body) diff --git a/emacsconf-schedule.el b/emacsconf-schedule.el index 7c9c060..7ce4fb5 100644 --- a/emacsconf-schedule.el +++ b/emacsconf-schedule.el @@ -149,7 +149,8 @@ Each function should take the info and manipulate it as needed, returning the ne (setq date (format-time-string "%Y-%m-%d" (date-to-time start-prop))) (setq start-prop (concat date " " start-prop))) (list - :scheduled (format-time-string (cdr org-time-stamp-formats) (date-to-time start-prop)) + :scheduled (format-time-string (cdr org-time-stamp-formats) (date-to-time start-prop) + emacsconf-timezone) :start-time (date-to-time start-prop) :fixed-time t)) (when track-prop diff --git a/emacsconf-stream.el b/emacsconf-stream.el index 91fbd81..19dcb32 100644 --- a/emacsconf-stream.el +++ b/emacsconf-stream.el @@ -168,7 +168,6 @@ while OTHER-FILENAME will be displayed at other times." (if (or (null (plist-get talk :q-and-a)) (string-match "Mumble" (plist-get talk :q-and-a))) (emacsconf-stream-open-in-between-slide talk) - (emacsconf-stream-open-pad talk) (emacsconf-stream-join-qa talk) (shell-command "i3-msg 'layout splith'"))))) @@ -178,58 +177,60 @@ while OTHER-FILENAME will be displayed at other times." (save-window-excursion (emacsconf-stream-set-talk-info talk)))) -(defun emacsconf-stream-play-intro-maybe (talk) - (interactive (list (emacsconf-complete-talk-info))) +(defun emacsconf-stream-track-ssh (talk &rest commands) + "SSH to the track account for TALK and run COMMANDS. +This might be more reliable than using TRAMP to call file processes, +especially when two things need to happen close together." + (setq talk (emacsconf-resolve-talk talk)) (let ((info (tramp-dissect-file-name (emacsconf-stream-track-login talk)))) (apply - #'call-process - (append - (list - "ssh" nil nil t - (concat (tramp-file-name-user info) - "@" (tramp-file-name-host info)) - "-p" (tramp-file-name-port info) - "nohup") - (list "intro" (plist-get talk :slug)) - (list ">" "/dev/null" "2>&1" "&"))))) + #'start-process + (delq nil + (append + (list + (concat "ssh-" (plist-get (emacsconf-get-track talk) :id)) + (concat "*" (plist-get talk :track) "*") "ssh" + (concat (tramp-file-name-user info) + "@" (tramp-file-name-host info)) + "-p" (tramp-file-name-port info) + (format "DISPLAY=%s" (plist-get (emacsconf-get-track talk) :vnc-display)) + "nohup") + (if (listp (car commands)) (car commands) commands)))))) + +(defun emacsconf-stream-play-intro (talk) + "Play the recorded intro or display the in-between slide for TALK." + (interactive (list (emacsconf-complete-talk-info))) + (setq talk (emacsconf-resolve-talk talk)) + (emacsconf-stream-track-ssh talk "intro" (plist-get talk :slug))) (defun emacsconf-stream-play-talk-on-change (talk) "Play the talk." (interactive (list (emacsconf-complete-talk-info))) + (setq talk (emacsconf-resolve-talk talk)) (when (or (not (boundp 'org-state)) (string= org-state "PLAYING")) - (let ((info (tramp-dissect-file-name (emacsconf-stream-track-login talk)))) - (apply - #'call-process - (append - (list - "ssh" nil nil t - (concat (tramp-file-name-user info) - "@" (tramp-file-name-host info)) - "-p" (tramp-file-name-port info) - (format "DISPLAY=%s" (plist-get (emacsconf-get-track talk) :vnc-display)) - "nohup") - (cond - ((and - (plist-get talk :recorded-intro) - (plist-get talk :video-file)) ;; recorded intro and recorded talk - (message "should automatically play intro and recording") - (list "play" (plist-get talk :slug))) ;; todo deal with stream files - ((and - (plist-get talk :recorded-intro) - (null (plist-get talk :video-file))) ;; recorded intro and live talk; play the intro and join BBB - (message "should automatically play intro; join %s" (plist-get talk :bbb-backstage)) - (list "intro" (plist-get talk :slug))) - ((and - (null (plist-get talk :recorded-intro)) - (plist-get talk :video-file)) ;; live intro and recorded talk, show slide and use Mumble; manually play talk - (message "should show intro slide; play %s afterwards" (plist-get talk :slug)) - (list "intro" (plist-get talk :slug))) - ((and - (null (plist-get talk :recorded-intro)) - (null (plist-get talk :video-file))) ;; live intro and live talk, join the BBB - (message "join %s for live intro and talk" (plist-get talk :bbb-backstage)) - (list "bbb" (plist-get talk :slug)))) - (list "&")))))) + (emacsconf-stream-track-ssh + talk + (cond + ((and + (plist-get talk :recorded-intro) + (plist-get talk :video-file)) ;; recorded intro and recorded talk + (message "should automatically play intro and recording") + (list "play-with-intro" (plist-get talk :slug))) ;; todo deal with stream files + ((and + (plist-get talk :recorded-intro) + (null (plist-get talk :video-file))) ;; recorded intro and live talk; play the intro and join BBB + (message "should automatically play intro; join %s" (plist-get talk :bbb-backstage)) + (list "intro" (plist-get talk :slug))) + ((and + (null (plist-get talk :recorded-intro)) + (plist-get talk :video-file)) ;; live intro and recorded talk, show slide and use Mumble; manually play talk + (message "should show intro slide; play %s afterwards" (plist-get talk :slug)) + (list "intro" (plist-get talk :slug))) + ((and + (null (plist-get talk :recorded-intro)) + (null (plist-get talk :video-file))) ;; live intro and live talk, join the BBB + (message "join %s for live intro and talk" (plist-get talk :bbb-backstage)) + (list "bbb" (plist-get talk :slug))))))) (defun emacsconf-stream-get-filename (talk) "Return the local filename for the video file for TALK. @@ -241,65 +242,79 @@ Final files should be stored in /data/emacsconf/stream/YEAR/video-slug--main.web (defun emacsconf-stream-play-video (talk) (interactive (list (emacsconf-complete-talk-info))) - (let ((info (tramp-dissect-file-name (emacsconf-stream-track-login talk)))) - (apply - #'call-process - (append - (list - "ssh" nil nil t - (concat (tramp-file-name-user info) - "@" (tramp-file-name-host info)) - "-p" (tramp-file-name-port info) - "nohup" "~/bin/track-mpv") - (or (and (plist-get talk :stream-files) - (if (stringp (plist-get talk :stream-files)) - (split-string-and-unquote (plist-get talk :stream-files)) - (plist-get talk :stream-files))) - (list (plist-get talk :slug))) - (list ">" "/dev/null" "2>&1" "&"))))) + (setq talk (emacsconf-resolve-talk talk)) + (emacsconf-stream-track-ssh + talk + (append + (list + "play" + (plist-get talk :slug)) + (if (stringp (plist-get talk :stream-files)) + (split-string-and-unquote (plist-get talk :stream-files)) + (plist-get talk :stream-files))))) + +(defun emacsconf-stream-play-video-file (talk filename) + (interactive (list (emacsconf-complete-talk-info))) + (setq talk (emacsconf-resolve-talk talk)) + (apply + #'emacsconf-stream-track-ssh + talk + "overlay" + (plist-get talk :slug)) + (apply + #'emacsconf-stream-track-ssh + talk + "mpv" + filename)) (defun emacsconf-stream-open-pad (talk) (interactive (list (emacsconf-complete-talk-info))) - (let ((default-directory (emacsconf-stream-track-login talk)) - (async-shell-command-buffer 'new-buffer)) - (shell-command - (concat "nohup firefox -new-window " - (shell-quote-argument (plist-get talk :pad-url)) - " > /dev/null 2>&1 & ")))) + (setq talk (emacsconf-resolve-talk talk)) + (emacsconf-stream-track-ssh + talk + "firefox" + (plist-get talk :pad-url))) + +(defun emacsconf-stream-bbb (talk) + "Open the BBB room for TALK." + (interactive (list (emacsconf-complete-talk-info))) + (setq talk (emacsconf-resolve-talk talk)) + (emacsconf-stream-track-ssh talk "bbb" (plist-get talk :slug))) (defun emacsconf-stream-join-qa (talk) "Join the Q&A for TALK. This uses the BBB room if available, or the IRC channel if not." (interactive (list (emacsconf-complete-talk-info))) (if (and (null (plist-get talk :video-file)) - (string-match "live" (plist-get talk :q-and-a))) - ;; do nothing, streamer should already be in the room - (let ((default-directory (emacsconf-stream-track-login talk)) - (async-shell-command-buffer 'new-buffer)) - (save-window-excursion - (shell-command - (concat "nohup firefox -new-window " - (shell-quote-argument - (pcase (plist-get talk :q-and-a) - ((or 'nil "" (rx "Mumble")) - (plist-get talk :qa-slide-url)) - ((rx "live") - (plist-get talk :bbb-backstage)) - ((rx "IRC") - (plist-get talk :webchat-url)) - (_ (plist-get talk :qa-slide-url)))) - " > /dev/null 2>&1 & ")))))) + (string-match "live" (plist-get talk :q-and-a))) + (emacsconf-stream-track-ssh + talk + "firefox" + "-new-window" + (plist-get talk :pad-url)) + (emacsconf-stream-track-ssh + talk + "firefox" + "-new-window" + (pcase (plist-get talk :q-and-a) + ((or 'nil "" (rx "Mumble")) + (plist-get talk :qa-slide-url)) + ((rx "live") + (plist-get talk :bbb-backstage)) + ((rx "IRC") + (plist-get talk :webchat-url)) + ((rx "pad") + (plist-get talk :pad-url)) + (_ (plist-get talk :qa-slide-url)))))) (defun emacsconf-stream-join-chat (talk) "Join the IRC chat for TALK." (interactive (list (emacsconf-complete-talk-info))) - (let ((default-directory (emacsconf-stream-track-login talk)) - (async-shell-command-buffer 'new-buffer)) - (shell-command - (concat "nohup firefox -new-window " - (shell-quote-argument - (plist-get talk :webchat-url)) - " > /dev/null 2>&1 & ")))) + (setq talk (emacsconf-resolve-talk talk)) + (emacsconf-stream-track-ssh + talk + "firefox" + (plist-get talk :webchat-url))) (defun emacsconf-stream-write-talk-overlay-svgs (talk video-filename other-filename) (setq talk (emacsconf-stream-add-talk-props talk)) @@ -502,21 +517,6 @@ 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)))) - (cancel-function-timers #'emacsconf-stream-handle-talk-timer) - (let ((now (current-time))) - (mapc (lambda (talk) - (when (and (time-less-p now (plist-get talk :start-time))) - (run-at-time - (time-to-seconds (time-subtract (plist-get talk :start-time) now)) - nil - #'emacsconf-stream-handle-talk-timer - talk))) - info))) - (defun emacsconf-stream-generate-assets-for-talk (talk) (interactive (list (emacsconf-complete-talk-info))) (let ((info (list talk))) @@ -741,5 +741,82 @@ ffplay URL (when (timerp emacsconf-stream-clock-timer) (cancel-timer emacsconf-stream-clock-timer)))) +;;; Timers + +(defvar emacsconf-stream-timers nil "List of timers for easy reference") + +(defun emacsconf-stream-cancel-timer (id) + "Cancel a timer by ID." + (interactive (list + (completing-read + "ID: " + (lambda (string pred action) + (if (eq action 'metadata) + `(metadata (display-sort-function . ,#'identity)) + (complete-with-action action + (sort + (seq-filter (lambda (o) + (and (timerp (cdr o)) + (not (timer--triggered (cdr o))))) + emacsconf-stream-timers) + (lambda (a b) (string< (car a) (car b)))) + string pred)))))) + (when (timerp (assoc-default id emacsconf-stream-timers)) + (cancel-timer (assoc-default id emacsconf-stream-timers)) + (setq emacsconf-stream-timers + (delq (assoc id emacsconf-stream-timers) + (seq-filter (lambda (o) + (and (timerp (cdr o)) + (not (timer--triggered (cdr o))))) + emacsconf-stream-timers))))) + +(defun emacsconf-stream-schedule-talk-status-change (talk time new-status) + "Schedule a one-off timer for TALK at TIME to set it to NEW-STATUS." + (interactive (list (emacsconf-complete-talk-info) + (read-string "Time: ") + (completing-read "Status: " (mapcar 'car emacsconf-status-types)))) + (require 'diary-lib) + (setq talk (emacsconf-resolve-talk talk)) + (let* ((converted + (cond + ((listp time) time) + ((timer-duration time) (timer-relative-time nil (timer-duration time))) + (t ; HH:MM + (date-to-time (concat (format-time-string "%Y-%m-%d" nil emacsconf-timezone) + "T" + (string-pad time 5 ?0 t) + emacsconf-timezone-offset))))) + (timer-id (concat (format-time-string "%m-%dT%H:%M" converted) + "-" + (plist-get talk :slug) + "-" + new-status))) + (emacsconf-stream-cancel-timer timer-id) + (add-to-list 'emacsconf-stream-timers + (cons + timer-id + (run-at-time time converted #'emacsconf-update-talk-status-with-hooks (plist-get talk :slug) "." new-status))))) + +(defun emacsconf-stream-schedule-timers (&optional info) + "Schedule PLAYING for the rest of talks and CLOSED_Q for recorded talks." + (interactive) + (emacsconf-stream-cancel-all-timers) + (setq info (emacsconf-prepare-for-display (emacsconf-filter-talks (or info (emacsconf-get-talk-info))))) + (let ((now (current-time))) + (mapc (lambda (talk) + (when (and (time-less-p now (plist-get talk :start-time))) + (emacsconf-stream-schedule-talk-status-change talk (plist-get talk :start-time) "PLAYING")) + (when (and + (plist-get talk :video-file) + (plist-get talk :qa-time) + (time-less-p now (plist-get talk :qa-time))) + (emacsconf-stream-schedule-talk-status-change talk (plist-get talk :qa-time) "CLOSED_Q"))) + info))) + +(defun emacsconf-stream-cancel-all-timers () + (interactive) + (cancel-function-timers #'emacsconf-update-talk-status-with-hooks) + (setq emacsconf-stream-timers nil)) + (provide 'emacsconf-stream) ;;; emacsconf-stream.el ends here diff --git a/emacsconf.el b/emacsconf.el index abd0052..214e896 100644 --- a/emacsconf.el +++ b/emacsconf.el @@ -533,8 +533,11 @@ (or (assoc-default (plist-get o :status) emacsconf-status-types 'string= "") (plist-get o :status))) - (if (member (plist-get o :status) - (split-string "PLAYING CLOSED_Q OPEN_Q UNSTREAMED_Q TO_ARCHIVE TO_EXTRACT TO_FOLLOW_UP DONE")) + (if (or + (member (plist-get o :status) + (split-string "PLAYING CLOSED_Q OPEN_Q UNSTREAMED_Q TO_ARCHIVE TO_EXTRACT TO_FOLLOW_UP DONE")) + (time-less-p (plist-get o :start-time) + (current-time))) (plist-put o :public t)) o) @@ -581,45 +584,52 @@ (null (plist-get o :email)) (string= (plist-get o :status) "CANCELLED") (string-match "after" (plist-get o :q-and-a))) - (if (string= (plist-get o :status) "WAITING_FOR_PREREC") - (plist-put o :live-time - (plist-get o :start-time)) - (progn - (plist-put - o :checkin-label - "1 hour before the scheduled start of your talk, since you don't have a pre-recorded video") - (plist-put - o :checkin-time - (time-subtract (plist-get o :start-time) (seconds-to-time 3600)))) - (plist-put o :live-time - (time-add (plist-get o :start-time) - (seconds-to-time (* 60 (string-to-number (plist-get o :video-time)))))) + (if (null (plist-get o :video-file)) + (progn + (plist-put o :live-time (plist-get o :start-time)) + (plist-put o :qa-time (plist-get o :live-time)) + (plist-put + o :checkin-label + "1 hour before the scheduled start of your talk, since you don't have a pre-recorded video") + (plist-put + o :checkin-time + (seconds-to-time (time-subtract (plist-get o :start-time) (seconds-to-time 3600))))) + (plist-put o :live-time + (seconds-to-time + (time-add (plist-get o :start-time) + (seconds-to-time (* 60 (string-to-number (plist-get o :video-time))))))) + (plist-put o :qa-time + (plist-get o :live-time)) + (plist-put o :checkin-label "30 minutes before the scheduled start of your Q&A, since you have a pre-recorded video") (when (plist-get o :video-time) (plist-put o :checkin-time - (time-subtract (time-add (plist-get o :start-time) - (seconds-to-time (* 60 (string-to-number (plist-get o :video-time))))) - (seconds-to-time (/ 3600 2))))))) + (seconds-to-time + (time-subtract (time-add (plist-get o :start-time) + (seconds-to-time (* 60 (string-to-number (plist-get o :video-time))))) + (seconds-to-time (/ 3600 2)))))))) o) (defun emacsconf-add-live-info (o) (plist-put o :absolute-url (concat emacsconf-base-url (plist-get o :url))) (plist-put o :in-between-url (format "%s%s/in-between/%s.png" - emacsconf-media-base-url - emacsconf-year - (plist-get o :slug))) + emacsconf-media-base-url + emacsconf-year + (plist-get o :slug))) (plist-put o :qa-slide-url (format "%s%s/in-between/%s.png" emacsconf-media-base-url emacsconf-year (plist-get o :slug))) + (plist-put o :intro-expanded (emacsconf-pad-expand-intro o)) (let ((track (emacsconf-get-track (plist-get o :track)))) (when track (plist-put o :watch-url (concat emacsconf-base-url emacsconf-year "/watch/" (plist-get track :id))) (plist-put o :webchat-url (concat emacsconf-chat-base "?join=emacsconf," (replace-regexp-in-string "#" "" - (plist-get track :channel))))) - (plist-put o :channel (plist-get track :channel)) + (plist-get track :channel)))) + (plist-put o :track-id (plist-get track :id))) + (plist-put o :channel (plist-get track :channel)) (plist-put o :bbb-backstage (concat emacsconf-media-base-url emacsconf-year "/backstage/current/room/" (plist-get o :slug))) (cond ((string= (or (plist-get o :q-and-a) "") "") @@ -734,6 +744,10 @@ (plist-put (pop cur-list) :next-talks (seq-take cur-list number))) info)) +(defun emacsconf-resolve-talk (talk) + "Return the plist for TALK." + (if (stringp talk) (emacsconf-find-talk-info talk) talk)) + (defun emacsconf-find-talk-info (filter &optional info) (setq info (or info (emacsconf-filter-talks (emacsconf-get-talk-info)))) (when (stringp filter) (setq filter (list filter))) @@ -1042,12 +1056,12 @@ :tramp "/ssh:emacsconf-dev@res.emacsconf.org#46668:" :stream ,(concat emacsconf-stream-base "dev.webm") :480p ,(concat emacsconf-stream-base "dev-480p.webm") - :start "09:00" :end "17:00" + :start "10:00" :end "17:00" :vnc-display ":6" :vnc-port "5906" :status "offline"))) -(setq emacsconf-shifts (list (list :id "sat-am-gen" :track "General" :start "2022-12-03T08:00:00-0500" :end "2022-12-03T12:00:00-0500" :host "zaeph" :streamer "sachac" :checkin "corwin" :irc "bandali" :pad "publicvoit" :coord "sachac") (list :id "sat-pm-gen" :track "General" :start "2022-12-03T13:00:00-0500" :end "2022-12-03T18:00:00-0500" :host "zaeph" :streamer "sachac" :checkin "FlowyCoder" :irc "dto" :pad "publicvoit" :coord "sachac") (list :id "sat-am-dev" :track "Development" :start "2022-12-03T08:00:00-0500" :end "2022-12-03T12:00:00-0500" :host "bandali" :streamer "sachac" :checkin "corwin" :irc "dto" :coord "sachac") (list :id "sat-pm-dev" :track "Development" :start "2022-12-03T13:00:00-0500" :end "2022-12-03T18:00:00-0500" :host "vetrivln" :streamer "bandali" :checkin "FlowyCoder" :irc "vetrivln" :coord "sachac") (list :id "sun-am-gen" :track "General" :start "2022-12-04T08:00:00-0500" :end "2022-12-04T12:00:00-0500" :host "zaeph" :streamer "sachac" :checkin "corwin" :irc "bandali" :pad "publicvoit" :coord "sachac") (list :id "sun-pm-gen" :track "General" :start "2022-12-04T13:00:00-0500" :end "2022-12-04T18:00:00-0500" :host "zaeph" :streamer "jman" :checkin "FlowyCoder" :irc "dto" :pad "publicvoit" :coord "sachac") (list :id "sun-am-dev" :track "Development" :start "2022-12-04T08:00:00-0500" :end "2022-12-04T12:00:00-0500" :host "bandali" :streamer "sachac" :checkin "corwin" :irc "dto" :coord "sachac") (list :id "sun-pm-dev" :track "Development" :start "2022-12-04T13:00:00-0500" :end "2022-12-04T18:00:00-0500" :host "vetrivln" :streamer "bandali" :checkin "FlowyCoder" :irc "vetrivln" :coord "sachac"))) +(setq emacsconf-shifts (list (list :id "sat-am-gen" :track "General" :start "2022-12-03T08:00:00-0500" :end "2022-12-03T12:00:00-0500" :host "zaeph" :streamer "sachac" :checkin "corwin" :irc "dto" :pad "publicvoit" :coord "sachac") (list :id "sat-pm-gen" :track "General" :start "2022-12-03T13:00:00-0500" :end "2022-12-03T18:00:00-0500" :host "zaeph" :streamer "sachac" :checkin "FlowyCoder" :irc "bandali" :pad "publicvoit" :coord "sachac") (list :id "sat-am-dev" :track "Development" :start "2022-12-03T08:00:00-0500" :end "2022-12-03T12:00:00-0500" :host "bandali" :streamer "sachac" :checkin "corwin" :irc "dto" :coord "sachac") (list :id "sat-pm-dev" :track "Development" :start "2022-12-03T13:00:00-0500" :end "2022-12-03T18:00:00-0500" :host "vetrivln" :streamer "bandali" :checkin "FlowyCoder" :irc "vetrivln" :coord "sachac") (list :id "sun-am-gen" :track "General" :start "2022-12-04T08:00:00-0500" :end "2022-12-04T12:00:00-0500" :host "zaeph" :streamer "sachac" :checkin "corwin" :irc "dto" :pad "publicvoit" :coord "sachac") (list :id "sun-pm-gen" :track "General" :start "2022-12-04T13:00:00-0500" :end "2022-12-04T18:00:00-0500" :host "zaeph" :streamer "jman" :checkin "FlowyCoder" :irc "bandali" :pad "publicvoit" :coord "sachac") (list :id "sun-am-dev" :track "Development" :start "2022-12-04T08:00:00-0500" :end "2022-12-04T12:00:00-0500" :host "bandali" :streamer "sachac" :checkin "corwin" :irc "dto" :coord "sachac") (list :id "sun-pm-dev" :track "Development" :start "2022-12-04T13:00:00-0500" :end "2022-12-04T18:00:00-0500" :host "vetrivln" :streamer "bandali" :checkin "FlowyCoder" :irc "vetrivln" :coord "sachac"))) (defun emacsconf-get-track (name) (when (listp name) (setq name (plist-get name :track))) @@ -1303,6 +1317,11 @@ tracks with the ID in the cdr of that list." (org-agenda-category-filter-preset (list (concat "+" (plist-get track :id))))) (org-agenda-list nil emacsconf-date 2))) +(defun emacsconf-update-talk-status-with-hooks (slug from-states to-state) + (interactive (list (emacsconf-complete-talk) "." (completing-read "To: " (mapcar 'car emacsconf-status-types)))) + (emacsconf-with-todo-hooks + (emacsconf-update-talk-status slug from-states to-state))) + (defun emacsconf-update-talk-status (slug from-states to-state) (interactive (list (emacsconf-complete-talk) "." (completing-read "To: " (mapcar 'car emacsconf-status-types)))) (emacsconf-with-talk-heading slug -- cgit v1.2.3