summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--emacsconf-erc.el114
-rw-r--r--emacsconf-extract.el325
-rw-r--r--emacsconf-hyperlist.el4
-rw-r--r--emacsconf-mail.el1121
-rw-r--r--emacsconf-pad.el269
-rw-r--r--emacsconf-publish.el935
-rw-r--r--emacsconf-schedule.el380
-rw-r--r--emacsconf-stream.el191
-rw-r--r--emacsconf-subed.el43
-rw-r--r--emacsconf.el440
10 files changed, 2842 insertions, 980 deletions
diff --git a/emacsconf-erc.el b/emacsconf-erc.el
index bced63d..bde801b 100644
--- a/emacsconf-erc.el
+++ b/emacsconf-erc.el
@@ -26,6 +26,15 @@
;;
;; Commands:
;;
+;; /opall
+;; /deopall
+;;
+;; general
+;; - /broadcast message
+;; - /conftopic message
+;;
+;; - /checkin nick talk
+;;
;; announcements only
;; - /nowplaying slug
;; - /nowclosedq slug
@@ -42,10 +51,6 @@
;; - /markunstreamedq slug
;; - /markdone slug
;;
-;; general
-;; - /broadcast message
-;; - /conftopic message
-;; - /checkin nick
;;
;;; Code:
@@ -63,12 +68,12 @@
(defcustom emacsconf-erc-org "#emacsconf-org" "Channel for organizers")
(defcustom emacsconf-topic-templates
- '(("#emacsconf" "Welcome to EmacsConf 2023 | please join our track-specific channels #emacsconf-gen and #emacsconf-dev as well | Subscribe to https://lists.gnu.org/mailman/listinfo/emacsconf-discuss for updates")
- ("#emacsconf-gen" "General track | https://emacsconf.org/2023/watch/gen/ | Subscribe to https://lists.gnu.org/mailman/listinfo/emacsconf-discuss for updates")
- ("#emacsconf-dev" "Development track | https://emacsconf.org/2023/watch/dev/ | Subscribe to https://lists.gnu.org/mailman/listinfo/emacsconf-discuss for updates")
- ("#emacsconf-accessible" "EmacsConf 2023 accessibility - help by describing what's happening | Subscribe to https://lists.gnu.org/mailman/listinfo/emacsconf-discuss for updates")
- ("#emacsconf-org" "EmacsConf 2023 | Dedicated channel for EmacsConf organizers and speakers | this is intended as an internal, low-traffic channel; for main discussion around EmacsConf, please join #emacsconf | Subscribe to https://lists.gnu.org/mailman/listinfo/emacsconf-discuss for updates")
- ("#emacsconf-questions" "EmacsConf 2023 | Low-traffic channel for questions if speakers prefer IRC and need help focusing; for main discussion around EmacsConf, please join #emacsconf | Subscribe to https://lists.gnu.org/mailman/listinfo/emacsconf-discuss for updates"))
+ '(("#emacsconf" "Welcome to EmacsConf 2025 | please join our track-specific channels #emacsconf-gen and #emacsconf-dev as well | Subscribe to https://lists.gnu.org/mailman/listinfo/emacsconf-discuss for updates")
+ ("#emacsconf-gen" "General track | https://emacsconf.org/2025/watch/gen/ | Subscribe to https://lists.gnu.org/mailman/listinfo/emacsconf-discuss for updates")
+ ("#emacsconf-dev" "Development track | https://emacsconf.org/2025/watch/dev/ | Subscribe to https://lists.gnu.org/mailman/listinfo/emacsconf-discuss for updates")
+ ("#emacsconf-accessible" "EmacsConf 2025 accessibility - help by describing what's happening | Subscribe to https://lists.gnu.org/mailman/listinfo/emacsconf-discuss for updates")
+ ("#emacsconf-org" "EmacsConf 2025 | Dedicated channel for EmacsConf organizers and speakers | this is intended as an internal, low-traffic channel; for main discussion around EmacsConf, please join #emacsconf | Subscribe to https://lists.gnu.org/mailman/listinfo/emacsconf-discuss for updates")
+ ("#emacsconf-questions" "EmacsConf 2025 | Low-traffic channel for questions if speakers prefer IRC and need help focusing; for main discussion around EmacsConf, please join #emacsconf | Subscribe to https://lists.gnu.org/mailman/listinfo/emacsconf-discuss for updates"))
"List of (channel topic-template) entries for mass-setting channel topics."
:group 'emacsconf
:type '(repeat (list (string :tag "Channel")
@@ -94,19 +99,20 @@ If MESSAGE is not specified, reset the topic to the template."
(cadr template)))))
emacsconf-topic-templates))
-(defun erc-cmd-CHECKIN (nick)
- (let* ((talk (emacsconf-complete-talk-info))
- (q-and-a (plist-get talk :q-and-a) ""))
- (save-excursion
- (emacsconf-with-talk-heading (plist-get talk :slug)
- (org-entry-put (point) "IRC" nick)
- (org-entry-put (point) "CHECK_IN" (format-time-string "%H:%M"))))
+(defun erc-cmd-CHECKIN (nick &optional talk)
+ (let* ((talk (if (stringp talk) (emacsconf-resolve-talk talk) (or talk (emacsconf-complete-talk-info))))
+ (q-and-a (or (plist-get talk :q-and-a) "")))
+ (save-window-excursion
+ (save-excursion
+ (emacsconf-with-talk-heading (plist-get talk :slug)
+ (org-entry-put (point) "IRC" nick)
+ (org-entry-put (point) "CHECK_IN" (format-time-string "%H:%M")))))
(cond
((string-match "live" q-and-a)
(erc-send-message (format "%s: Thanks for checking in! I'll send you some private messages with instructions, so please check there. Let me know if you don't get them." nick))
(erc-cmd-ROOM nick talk))
((string-match "pad" q-and-a)
- (erc-send-message (format "%s: Thanks for checking in! The collaborative pad we'll be using for questions is at %s . We'll collect questions and put them there. If you'd like to open it, you can keep an eye on questions. Please let us know if you need help, or if you want to switch to live Q&A." nick (plist-get talk :pad-url))))
+ (erc-send-message (format "%s: Thanks for checking in! The collaborative pad we'll be using for questions is at %s . We'll collect questions and put them there. If you'd like to open it, you can keep an eye on questions. You can answer questions in any order or skip anything you want to save for later. Please let us know if you need help, or if you want to switch to live Q&A." nick (plist-get talk :pad-url))))
((string-match "IRC" q-and-a)
(erc-send-message (format "#%s: Thanks for checking in! Feel free to keep an eye on %s for questions and discussion, and we'll copy things from the pad to there. If the volume gets overwhelming, let us know and we can /msg you questions or add them to the pad. If you'd like to try Q&A over live video or the collaborative pad instead, or if you need help, please let us know." nick
(plist-get (emacsconf-get-track (plist-get talk :track)) :channel))))
@@ -115,11 +121,37 @@ If MESSAGE is not specified, reset the topic to the template."
(emacsconf-with-talk-heading (plist-get talk :slug)
(emacsconf-upcoming-insert-or-update nil t)))))
-(defun erc-cmd-BBB (nick &optional talk)
+(defun erc-cmd-BACKSTAGE (nick)
+ "Send NICK emergency backstage information."
+ (erc-send-message (format "%s: I'll send you a private message on IRC with the backstage details."))
+ (erc-message
+ "PRIVMSG"
+ (format "%s You can access the backstage area at %s%s/backstage/ with the username \"%s\" and the password \"%s\" (please keep them secret, thanks!). The general talk index is at %s%s/backstage/index-gen.html and the dev talk index is at %s%s/backstage/index-dev.html"
+ nick
+ emacsconf-media-base-url
+ emacsconf-year
+ emacsconf-backstage-user
+ emacsconf-backstage-password
+ emacsconf-media-base-url
+ emacsconf-year
+ emacsconf-media-base-url
+ emacsconf-year
+ )))
+
+(defun erc-cmd-PAD (nick &optional talk)
+ (setq talk (if (stringp talk) (emacsconf-resolve-talk talk)
+ (or talk (emacsconf-complete-talk-info))))
+ (erc-message "PRIVMSG" (format "%s The collaborative pad we'll be using for questions is at %s . We'll collect questions from %s and put them there. When you're live, you can answer questions in any order or skip anything you want to save for later. Alternatively, we can read questions to you. Let us know what you'd prefer!" nick (plist-get talk :pad-url) (plist-get talk :channel))))
+
+(defun erc-cmd-ROOM (nick &optional talk)
"Send live Q&A instructions to NICK."
- (setq talk (or talk (emacsconf-complete-talk-info)))
- (erc-message "PRIVMSG" (format "%s You can use this BBB room: %s . We'll join you there shortly to set up the room and do the last-minute tech check." nick (plist-get talk :bbb-room)))
- (erc-message "PRIVMSG" (format "%s The collaborative pad we'll be using for questions is at %s . We'll collect questions from #emacsconf and put them there. If you'd like to jump to your part of the document, you might be able to keep an eye on questions. Alternatively, we can read questions to you." nick (plist-get talk :pad-url)))
+ (setq talk (if (stringp talk) (emacsconf-resolve-talk talk)
+ (or talk (emacsconf-complete-talk-info))))
+ (erc-message
+ "PRIVMSG"
+ (if (plist-get talk :bbb-mod-code)
+ (format "%s You can use this BBB room: %s . If you use the moderator access code \"%s\" when you log in, you can get all set up to share your screen if you want. We'll join you there shortly to do the last-minute tech check." nick (plist-get talk :bbb-room) (plist-get talk :bbb-mod-code))
+ (format "%s You can use this BBB room: %s . We'll join you there shortly to set up the room and do the last-minute tech check." nick (plist-get talk :bbb-room))))
(erc-message "PRIVMSG" (format "%s The host will join and give you the go-ahead when you go on air. See you in the BBB room!" nick)))
(defun erc-cmd-READY (&rest filter)
@@ -183,10 +215,10 @@ If MESSAGE is not specified, reset the topic to the template."
(emacsconf-erc-with-channels (list (concat "#" (plist-get talk :channel)))
(erc-cmd-TOPIC
(format
- "%s: %s (%s) pad: %s Q&A: %s | %s"
+ "%s: %s%s pad: %s Q&A: %s | %s"
(plist-get talk :slug)
(plist-get talk :title)
- (plist-get talk :speakers)
+ (emacsconf-surround " (" (plist-get talk :speakers) ")" " -")
(plist-get talk :pad-url)
(plist-get talk :qa-info)
(car (assoc-default
@@ -195,7 +227,10 @@ If MESSAGE is not specified, reset the topic to the template."
(erc-send-message (format "---- %s: %s - %s ----"
(plist-get talk :slug)
(plist-get talk :title)
- (plist-get talk :speakers-with-pronouns)))
+ (emacsconf-surround " - " (plist-get talk :speakers-with-pronouns) "" "")))
+ (erc-send-message
+ (concat "Talk page: "
+ (plist-get talk :absolute-url)))
(erc-send-message
(concat "Add your notes/questions to the pad: " (plist-get talk :pad-url)))
(cond
@@ -515,5 +550,34 @@ Usage: /conflog keyword notes go here"
(cons (cons (match-string-no-properties 1 string) (current-time))
(seq-take emacsconf-erc-recent-announcements (1- emacsconf-erc-recent-announcements-length))))))
+;; (keymap-set erc-mode-map "C-c w" #'emacsconf-erc-copy)
+(defun emacsconf-erc-copy (&optional beg end)
+ "Unwrap and copy the current line to the clipboard.
+This makes it easier to paste into the Etherpad."
+ (interactive
+ (list
+ (if (region-active-p)
+ (min (point) (mark)))
+ (if (region-active-p)
+ (max (point) (mark)))))
+ (setq beg (or beg (if (get-text-property (point) 'erc--ts)
+ (line-beginning-position)
+ (prop-match-beginning (text-property-search-backward 'erc--ts)))))
+ (setq end
+ (let ((end-field (save-excursion (text-property-search-forward 'field)))
+ (end-nick (save-excursion (text-property-search-forward 'erc--ts nil nil t))))
+ (min (if end-field (prop-match-beginning end-field) most-positive-fixnum)
+ (if end-nick (prop-match-beginning end-nick) most-positive-fixnum))))
+ (let* ((pulse-flag nil))
+ (when (fboundp 'pulse-momentary-highlight-region)
+ (pulse-momentary-highlight-region beg end))
+ (kill-new
+ (string-trim
+ (replace-regexp-in-string
+ "\n[ \t]+" " "
+ (buffer-substring-no-properties
+ beg
+ end))))))
+
(provide 'emacsconf-erc)
;;; emacsconf-erc.el ends here
diff --git a/emacsconf-extract.el b/emacsconf-extract.el
index efd2ade..5600494 100644
--- a/emacsconf-extract.el
+++ b/emacsconf-extract.el
@@ -247,6 +247,11 @@
(gethash "sentences" data)))))
;; (emacsconf-extract-qa-from-assemblyai-sentences "~/proj/emacsconf/rms/sentences")
+(defun emacsconf-extract-unescape (s)
+ (replace-regexp-in-string
+ "\\\\\\(['\"]\\)"
+ "\\1" s))
+
;;;###autoload
(defun emacsconf-extract-copy-pad-to-wiki ()
"Copy the notes and questions from the current file to the wiki page for this talk."
@@ -268,12 +273,12 @@
nil
(re-search-forward "-after)" nil t)
(forward-line -1)
- (insert "# Discussion\n\n"))
- (save-excursion
- (unless (string= (or questions "") "")
- (insert "## Questions and answers\n\n" questions "\n\n"))
- (unless (string= (or notes "") "")
- (insert "## Notes\n\n" notes "\n\n")))))
+ (insert "# Discussion\n\n")
+ (save-excursion
+ (unless (string= (or questions "") "")
+ (insert "## Questions and answers\n\n" (emacsconf-extract-unescape questions) "\n\n"))
+ (unless (string= (or notes "") "")
+ (insert "## Notes\n\n" (emacsconf-extract-unescape notes) "\n\n"))))))
(defun emacsconf-extract-question-headings (slug)
(with-temp-buffer
@@ -308,7 +313,7 @@
"Question: "
(emacsconf-extract-question-headings
(emacsconf-get-slug-from-string (file-name-base (buffer-file-name)))))))
- (insert "NOTE " question "\n\n"))
+ (subed-set-subtitle-comment (concat "Q: " question)))
(defun emacsconf-extract-wget-bbb (o)
(when (plist-get o :bbb-playback)
@@ -355,7 +360,8 @@
(date-to-time
(dom-text
(dom-by-tag
- (dom-elements dom 'eventname "StopRecordingEvent")
+ (or (dom-elements dom 'eventname "StopRecordingEvent")
+ (dom-elements dom 'eventname "EndAndKickAllEvent"))
'date))))
(setq start-ms (* 1000 (time-to-seconds start-recording))
stop-ms (* 1000 (time-to-seconds stop-recording)))
@@ -428,11 +434,28 @@
(let ((results ""))
(save-excursion
(goto-char (point-min))
- (while (re-search-forward "^\\( *- \\([QA]: \\)?\\)\\[[0-9:]+\\] <.*?> \\(.*\n\\)" nil t)
- (setq results (concat results (match-string 1) (match-string 3)))
+ (while (re-search-forward "^\\([qna] *\\| *- +\\([QA]: \\)?\\)\\[[0-9:]+\\] <.*?> \\(.*\n\\)" nil t)
+ (setq results (concat results
+ (save-match-data
+ (pcase (match-string 1)
+ ((rx "q") "- Q: ")
+ ((rx "a") "- A: ")
+ ((rx "n") "- ")
+ (_ "- ")))
+ (match-string 3)))
(replace-match "" nil t nil 1))
(kill-new results))))
+(defvar-keymap emacsconf-extract-irc-log-map
+ "<down>" #'forward-line
+ "<up>" #'previous-line
+ "<right>" (lambda () (interactive) (insert "-") (forward-line))
+ "<left>" #'forward-line)
+
+(defun emacsconf-extract-irc-log ()
+ (interactive)
+ (set-transient-map emacsconf-extract-irc-log-map t))
+
(defun emacsconf-extract-irc-backward-by-nick ()
(interactive)
(goto-char (line-beginning-position))
@@ -503,29 +526,12 @@
(defun emacsconf-extract-irc-anonymize-log (beg end speakers)
(interactive "r\nMNick(s): ")
(when (stringp speakers) (setq speakers (split-string speakers)))
- (let ((text (buffer-substring beg end))
- nicks)
- (with-temp-buffer
- (insert text)
- (goto-char (point-min))
- ;; make a list of nicks
- (while (re-search-forward "^\\[[0-9:]+\\] <\\(.*?\\)>" nil t)
- (unless (member (match-string 1) speakers)
- (add-to-list 'nicks (match-string 1))))
- (goto-char (point-min))
- (while (re-search-forward "^\\[[0-9:]+\\] <\\(.*?\\)> \\(.+\\)" nil t)
- (replace-match
- (if (member (match-string 1) speakers)
- (concat " - A: " (match-string 2))
- (format "- {{%d}} %s"
- (seq-position nicks (match-string 1))
- (propertize (match-string 2)
- 'nick (match-string 1))))))
- (goto-char (point-min))
- (perform-replace (regexp-opt nicks) (lambda ()))
- (setq text (buffer-string))
- (other-window 1)
- (insert text))))
+ (save-excursion
+ (goto-char beg)
+ (while (re-search-forward "^\\[[0-9:]+\\] <\\(.*?\\)> \\(.+\\)" end t)
+ (if (member (match-string 1) speakers)
+ (replace-match (concat "- " (match-string 1) ": " (match-string 2)) t t)
+ (replace-match (concat "- " (match-string 2)) t t)))))
(defun emacsconf-private-qa (&optional info)
(seq-remove (lambda (o)
@@ -720,7 +726,7 @@ Would you like to help? See [[help_with_chapter_markers]] for more details. You
(setq talk (emacsconf-resolve-talk talk))
(expand-file-name "events.xml" (expand-file-name (plist-get talk :bbb-meeting-id) emacsconf-extract-bbb-raw-dir)))
-(defun emacsconf-extract-bbb-report ()
+(defun emacsconf-extract-bbb-report (&optional event-xml-files)
(let* ((max 0)
(participant-count 0)
(meeting-count 0)
@@ -730,39 +736,41 @@ Would you like to help? See [[help_with_chapter_markers]] for more details. You
(meeting-events
(sort
(seq-mapcat
- (lambda (talk)
- (when (plist-get talk :bbb-meeting-id)
- (let ((dom (xml-parse-file (emacsconf-extract-bbb-raw-events-file-name talk)))
- participants talking meeting-events)
- (mapc (lambda (o)
- (pcase (dom-attr o 'eventname)
- ("ParticipantJoinEvent"
- (cl-pushnew (cons (dom-text (dom-by-tag o 'userId))
- (dom-text (dom-by-tag o 'name)))
- participants)
- (push (cons (string-to-number (dom-text (dom-by-tag o 'timestampUTC)))
- (dom-attr o 'eventname))
- meeting-events))
- ("ParticipantLeftEvent"
- (when (string= (dom-attr o 'module) "PARTICIPANT")
- (push (cons (string-to-number (dom-text (dom-by-tag o 'timestampUTC)))
- (dom-attr o 'eventname))
- meeting-events)))
- ("ParticipantTalkingEvent"
- (cl-pushnew (assoc-default (dom-text (dom-by-tag o 'participant)) participants) talking))
- ((or
- "CreatePresentationPodEvent"
- "EndAndKickAllEvent")
+ (lambda (file)
+ (let ((dom (xml-parse-file file))
+ participants talking meeting-events)
+ (mapc (lambda (o)
+ (pcase (dom-attr o 'eventname)
+ ("ParticipantJoinEvent"
+ (cl-pushnew (cons (dom-text (dom-by-tag o 'userId))
+ (dom-text (dom-by-tag o 'name)))
+ participants)
+ (push (cons (string-to-number (dom-text (dom-by-tag o 'timestampUTC)))
+ (dom-attr o 'eventname))
+ meeting-events))
+ ("ParticipantLeftEvent"
+ (when (string= (dom-attr o 'module) "PARTICIPANT")
(push (cons (string-to-number (dom-text (dom-by-tag o 'timestampUTC)))
(dom-attr o 'eventname))
- meeting-events))))
- (dom-search dom (lambda (o) (dom-attr o 'eventname))))
- (cl-pushnew (list :slug (plist-get talk :slug)
- :participants participants
- :talking talking)
- meeting-participants)
- meeting-events)))
- (emacsconf-get-talk-info))
+ meeting-events)))
+ ("ParticipantTalkingEvent"
+ (cl-pushnew (assoc-default (dom-text (dom-by-tag o 'participant)) participants) talking))
+ ((or
+ "CreatePresentationPodEvent"
+ "EndAndKickAllEvent")
+ (push (cons (string-to-number (dom-text (dom-by-tag o 'timestampUTC)))
+ (dom-attr o 'eventname))
+ meeting-events))))
+ (dom-search dom (lambda (o) (dom-attr o 'eventname))))
+ (cl-pushnew (list ;; :slug (plist-get talk :slug)
+ :participants participants
+ :talking talking)
+ meeting-participants)
+ meeting-events))
+ (or event-xml-files
+ (mapcar #'emacsconf-extract-bbb-raw-events-file-name
+ (seq-filter (lambda (talk) (plist-get talk :bbb-meeting-id))
+ (emacsconf-get-talk-info)))))
(lambda (a b) (< (car a) (car b))))))
(dolist (event meeting-events)
(pcase (cdr event)
@@ -794,13 +802,8 @@ Would you like to help? See [[help_with_chapter_markers]] for more details. You
(expand-file-name (plist-get talk :bbb-meeting-id) emacsconf-extract-bbb-published-dir))))
-(defun emacsconf-extract-bbb-parse-events (talk)
- "Parse events TALK from raw recordings.
-This works with the events.xml from /var/bigbluebutton/raw.
-Files should be downloaded to `emacsconf-extract-bbb-raw-dir'."
- (setq talk (emacsconf-resolve-talk talk))
- (let* ((xml-file (emacsconf-extract-bbb-raw-events-file-name talk))
- (dom (xml-parse-file xml-file))
+(defun emacsconf-extract-bbb-parse-events (xml-file)
+ (let* ((dom (xml-parse-file xml-file))
(meeting-name (dom-attr (dom-by-tag dom 'metadata) 'meetingName))
(meeting-id (dom-attr dom 'meeting_id))
(conf-joined (dom-search dom (lambda (o) (and (string= (dom-tag o) "name") (string= (dom-text o) emacsconf-extract-conference-username)))))
@@ -886,6 +889,37 @@ Files should be downloaded to `emacsconf-extract-bbb-raw-dir'."
(talking . ,(nreverse talking))
(chat . ,(nreverse chat)))))
+(defun emacsconf-extract-bbb-talking-report (meeting-xml)
+ (let ((data (emacsconf-extract-bbb-parse-events meeting-xml)))
+ (unless (string= "" (alist-get 'meeting-date data))
+ (format "- %s %s: %s\n"
+ (alist-get 'name data)
+ (format-time-string "%a %I:%M %p"
+ (date-to-time
+ (alist-get 'meeting-date data)))
+ (mapconcat
+ (lambda (person)
+ (format "%s (%s)"
+ (car person)
+ (/ (cdr person) 60000)))
+ (sort
+ (mapcar
+ (lambda (group)
+ (cons
+ (car group)
+ (apply '+ (mapcar (lambda (o) (- (elt o 2) (elt o 1))) (cdr group)))))
+ (seq-group-by 'car (alist-get 'talking data)))
+ :key 'cdr
+ :reverse t)
+ ", ")))))
+
+(defun emacsconf-extract-bbb-parse-events-for-talk (talk)
+ "Parse events TALK from raw recordings.
+This works with the events.xml from /var/bigbluebutton/raw.
+Files should be downloaded to `emacsconf-extract-bbb-raw-dir'."
+ (setq talk (emacsconf-resolve-talk talk))
+ (emacsconf-extract-bbb-parse-events (emacsconf-extract-bbb-raw-events-file-name talk)))
+
(defun emacsconf-extract-bbb-format-chat ()
(mapconcat
(lambda (events)
@@ -1127,6 +1161,10 @@ Strategies:
;; To avoid being prompted for the client secret, it's helpful to have a line in ~/.authinfo or ~/.authinfo.gpg with
;; machine https://oauth2.googleapis.com/token username CLIENT_ID password CLIENT_SECRET
+;; reset:
+;; (setq url-http-oauth--interposed nil url-http-oauth--interposed-regexp nil)
+;; and remove the token from ~/.authinfo
+
(defvar emacsconf-extract-google-client-identifier nil)
(defvar emacsconf-extract-youtube-api-channels nil)
(defvar emacsconf-extract-youtube-api-categories nil)
@@ -1154,7 +1192,7 @@ Strategies:
("access_type" . "offline")
("prompt" . "consent")))
("access-token-endpoint" . "https://oauth2.googleapis.com/token")
- ("scope" . "https://www.googleapis.com/auth/youtube")
+ ("scope" . "https://www.googleapis.com/auth/youtube https://www.googleapis.com/auth/youtube.force-ssl https://www.googleapis.com/auth/youtube.upload")
("client-secret-method" . prompt))))
(setq emacsconf-extract-youtube-api-channels
(plz 'get "https://youtube.googleapis.com/youtube/v3/channels?part=contentDetails&mine=true"
@@ -1180,6 +1218,106 @@ Strategies:
(string-match (regexp-quote emacsconf-year)
(let-alist item .snippet.title))))))
+(defun emacsconf-extract-youtube-comment-list ()
+ (seq-mapcat
+ (lambda (item)
+ (append
+ (if (alist-get 'topLevelComment (alist-get 'snippet item))
+ (list (alist-get 'topLevelComment (alist-get 'snippet item))))
+ (alist-get 'comments (alist-get 'replies item))))
+ (alist-get
+ 'items
+ (or emacsconf-extract-youtube-comments (emacsconf-extract-youtube-get-channel-comments)))))
+
+(defun emacsconf-extract-youtube-get-talk-for-video-id (video-id)
+ (seq-find (lambda (o)
+ (or (string-match (regexp-quote video-id) (or (plist-get o :youtube-url) ""))
+ (string-match (regexp-quote video-id) (or (plist-get o :qa-youtube-url) ""))))
+ (emacsconf-get-talk-info)))
+
+(defun emacsconf-extract-youtube-comments-after (date)
+ (interactive (list (org-read-date nil t nil "On or after date: ")))
+ (when (stringp date)
+ (setq date (org-read-date nil t date)))
+ (seq-filter
+ (lambda (entry)
+ (time-less-p
+ date
+ (date-to-time
+ (alist-get
+ 'publishedAt
+ (alist-get 'snippet entry)))))
+ (emacsconf-extract-youtube-comment-list)))
+
+(defun emacsconf-extract-youtube-format-talk-comments (videos)
+ (mapconcat
+ (lambda (video)
+ (format
+ "- https://youtu.be/%s\n%s\n"
+ (car video)
+ (mapconcat
+ (lambda (comment)
+ (let-alist comment
+ (format
+ " - %s: %s\n"
+ .snippet.authorDisplayName
+ (replace-regexp-in-string "\n" "\n " .snippet.textOriginal))))
+ (cdr video)
+ "")))
+ videos
+ ""))
+
+(defun emacsconf-extract-youtube-comments-by-talk (&optional comments)
+ (interactive (list
+ (if current-prefix-arg (emacsconf-extract-youtube-comments-after (org-read-date nil nil nil "Date: ")))))
+ (setq comments (or comments (emacsconf-extract-youtube-comment-list)))
+ (let ((by-talk
+ (seq-group-by
+ (lambda (group)
+ (plist-get (emacsconf-extract-youtube-get-talk-for-video-id (car group)) :slug))
+ (seq-group-by (lambda (o)
+ (alist-get 'videoId (alist-get 'snippet o)))
+ comments))))
+ (when (called-interactively-p 'any)
+ (with-current-buffer (get-buffer-create "*comments*")
+ (erase-buffer)
+ (org-mode)
+ (dolist (group by-talk)
+ (when (car group)
+ (insert (format
+ "* %s\n\n%s\n\n"
+ (org-link-make-string
+ (concat "file:"
+ (expand-file-name
+ (concat
+ (car group) ".md")
+ (expand-file-name
+ "talks"
+ (expand-file-name
+ emacsconf-year
+ emacsconf-directory))))
+ (car group))
+ (emacsconf-extract-youtube-format-talk-comments (cdr group))))))
+ (display-buffer (current-buffer))))
+ by-talk))
+
+
+;; (emacsconf-extract-youtube-comment-list)
+
+;; (emacsconf-extract-youtube-comments-after "-2mon")
+
+(defvar emacsconf-extract-youtube-comments nil)
+(defun emacsconf-extract-youtube-get-channel-comments (&optional no-cache)
+ (setq
+ emacsconf-extract-youtube-comments
+ (or (and emacsconf-extract-youtube-comments (not no-cache))
+ (plz 'get
+ (format
+ "https://youtube.googleapis.com/youtube/v3/commentThreads?part=snippet,replies&allThreadsRelatedToChannelId=%s&maxResults=100"
+ (alist-get 'id (car (alist-get 'items emacsconf-extract-youtube-api-channels))))
+ :headers `(("Authorization" . ,(url-oauth-auth "https://youtube.googleapis.com/youtube/v3/")))
+ :as #'json-read))))
+
(defvar emacsconf-extract-youtube-tags '("emacs" "emacsconf"))
(defun emacsconf-extract-youtube-object (video-id talk &optional privacy-status qa)
"Format the video object for VIDEO-ID using TALK details.
@@ -1206,19 +1344,27 @@ If QA is non-nil, treat it as a Q&A video."
(let-alist video-object
(cond
;; not yet renamed
- ((string-match (rx (literal emacsconf-id) " " (literal emacsconf-year) " "
- (group (1+ (or (syntax word) "-")))
- " ")
- .snippet.title)
+ ((and .snippet.title (string-match (rx (literal emacsconf-id) " " (literal emacsconf-year) " "
+ (group (1+ (or (syntax word) "-")))
+ " ")
+ .snippet.title))
(match-string 1 .snippet.title))
;; renamed, match the description instead
- ((string-match (rx (literal emacsconf-base-url) (literal emacsconf-year) "/talks/"
- (group (1+ (or (syntax word) "-"))))
- .snippet.description)
+ ((and .snippet.description
+ (string-match (rx (literal emacsconf-base-url) (literal emacsconf-year) "/talks/"
+ (group (1+ (or (syntax word) "-"))))
+ .snippet.description))
(match-string 1 .snippet.description))
(t
(plist-get
- (seq-find (lambda (o) (string-match (regexp-quote .snippet.resourceId.videoId) (or (plist-get o :youtube-url) "")))
+ (seq-find (lambda (o)
+ (or
+ (string-match (regexp-quote (or .snippet.videoId
+ .snippet.resourceId.videoId))
+ (or (plist-get o :youtube-url) ""))
+ (string-match (regexp-quote (or .snippet.videoId
+ .snippet.resourceId.videoId))
+ (or (plist-get o :qa-youtube-url) ""))))
(emacsconf-get-talk-info))
:slug)))))
@@ -1261,6 +1407,7 @@ If QA is non-nil, treat it as a Q&A video."
(if (<= num-pages 0) (setq url null))))
result))
+
(defun emacsconf-extract-youtube-api-update-video (video-object &optional qa)
"Update VIDEO-OBJECT.
If QA is non-nil, treat it as a Q&A video."
@@ -1419,6 +1566,8 @@ If QA is non-nil, treat it as a Q&A video."
:as #'json-read))
nil)))))
+
+
(defun emacsconf-extract-youtube-duration-msecs (video)
(let-alist video
(when-let ((duration .contentDetails.duration))
@@ -1672,5 +1821,19 @@ Call with a prefix arg to store the URL as Q&A."
url)))))
+(defun emacsconf-extract-subed-copy-section-text ()
+ (interactive)
+ (save-excursion
+ (subed-copy-region-text
+ (unless (looking-at "^NOTE")
+ (if (re-search-backward "^NOTE" nil t)
+ (point)
+ (point-min)))
+ (progn
+ (forward-line)
+ (if (re-search-forward "^NOTE" nil t)
+ (match-beginning 0)
+ (point-max))))))
+
(provide 'emacsconf-extract)
;;; emacsconf-extract.el ends here
diff --git a/emacsconf-hyperlist.el b/emacsconf-hyperlist.el
index df1ed02..bbc3f41 100644
--- a/emacsconf-hyperlist.el
+++ b/emacsconf-hyperlist.el
@@ -138,7 +138,7 @@
(defun emacsconf-hyperlist-day-events (day &optional track info)
(let* ((talks
- (emacsconf-prepare-for-display
+ (emacsconf-publish-prepare-for-display
(emacsconf-filter-talks-by-time
(concat day "T00:00:00" emacsconf-timezone-offset)
(concat day "T23:59:59" emacsconf-timezone-offset)
@@ -163,7 +163,7 @@
(time-less-p (car a) (car b))))))
(defun emacsconf-hyperlist-format-streamer-day (day &optional track info)
- (setq info (emacsconf-prepare-for-display
+ (setq info (emacsconf-publish-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)))
diff --git a/emacsconf-mail.el b/emacsconf-mail.el
index 190ca4b..03b8261 100644
--- a/emacsconf-mail.el
+++ b/emacsconf-mail.el
@@ -238,17 +238,24 @@ Group by e-mail. With prefix argument (e.g. \\[universal-argument]),
insert into the current buffer instead of drafting e-mails."
(interactive "P")
(let* ((mail-func (emacsconf-mail-complete-template-function))
- (grouped (emacsconf-mail-group-by-email))
+ (grouped (emacsconf-filter-talks-by-logbook
+ (symbol-name mail-func)
+ (emacsconf-mail-group-by-email)))
(emacsconf-mail-prepare-behavior (if arg t 'new-message)))
(mapc (lambda (group)
- (funcall mail-func group))
+ (funcall mail-func group)
+ (mapc (lambda (talk)
+ (emacsconf-mail-log-message-when-sent talk (symbol-name mail-func)))
+ (cdr group)))
grouped)))
(defun emacsconf-mail-log-message-when-sent (o message)
(add-hook 'message-sent-hook
`(lambda ()
(save-window-excursion
- (emacsconf-add-to-talk-logbook ,(plist-get o :slug) ,message)))
+ (emacsconf-add-to-talk-logbook ,(plist-get o :slug) ,message))
+ (when (match-buffers "*unsent")
+ (switch-to-buffer (car (match-buffers "*unsent")))))
nil t))
(defun emacsconf-mail-group-by-email (&optional info)
@@ -356,7 +363,8 @@ insert into the current buffer instead of drafting e-mails."
(defun emacsconf-mail-parse-submission (body)
"Extract data from EmacsConf submissions in BODY."
- (when (listp body) (setq body (plist-get (car body) :content)))
+ (while (and (listp body) (plist-get (car body) :content))
+ (setq body (plist-get (car body) :content)))
(let* ((data (list :body body))
(fields '((:title "^[* ]*Talk title")
(:description "^[* ]*Talk description")
@@ -534,6 +542,22 @@ Include some other things, too, such as emacsconf-year, title, name, email, url,
(kill-buffer buffer)))))
(buffer-list)))
+(defun emacsconf-mail-merge-send-all ()
+ "Send all the unsent messages."
+ (interactive)
+ (mapc (lambda (buffer)
+ (with-current-buffer buffer
+ (message-send-and-exit)))
+ (match-buffers "unsent")))
+
+(defun emacsconf-mail-merge-clean-up ()
+ "Kill all the sent messages."
+ (interactive)
+ (mapc (lambda (buffer)
+ (when (string-match "^\\*sent mail" (buffer-name buffer))
+ (kill-buffer buffer)))
+ (buffer-list)))
+
;;; Notmuch
;;;###autoload
@@ -559,6 +583,22 @@ Include some other things, too, such as emacsconf-year, title, name, email, url,
"Volunteers: " (emacsconf-volunteer-emails-for-completion))))
(compose-mail (string-join volunteers ", ")))
+(defun emacsconf-mail-core ()
+ (interactive)
+ (let ((people
+ (seq-remove
+ (lambda (o)
+ (string= user-mail-address (assoc-default "EMAIL" o 'string=)))
+ (emacsconf-get-volunteer-info "core"))))
+ (compose-mail
+ (mapconcat (lambda (o) (assoc-default "EMAIL" o 'string=)) people ", "))
+ (message-goto-body)
+ (insert
+ "Hello, "
+ (string-join (sort (mapcar (lambda (o) (assoc-default "NAME_SHORT" o 'string=)) people)) ", ")
+ "!\n\n")
+ (message-goto-subject)))
+
(defun emacsconf-mail-notmuch-search-for-volunteer (volunteer)
(interactive
(list
@@ -600,7 +640,7 @@ This includes NAME_SHORT and EMAIL_NOTES."
:body "
Hi, ${speakers-short}!
-Thanks for submitting your proposal! (ZZZ TODO: feedback)
+Thanks for submitting your proposal!
We'll wait a week (~ ${notification-date}) in case the other volunteers want to chime in regarding your talk. =)
@@ -617,6 +657,99 @@ ${signature}
:year emacsconf-year
:notification-date (plist-get talk :date-to-notify))))
+(defun emacsconf-mail-send-grouped-acceptance (group &optional arg)
+ "Send acceptance letter to speakers.
+GROUP is (email . (talk talk talk)).
+If called with ARG, insert into current buffer instead of composing or updating a message."
+ (interactive (list (emacsconf-mail-complete-email-group
+ (seq-filter (lambda (o) (and
+ (plist-get o :email)
+ (string= (plist-get o :status) "TO_ACCEPT")))
+ (emacsconf-get-talk-info)))
+ current-prefix-arg))
+ (let ((emacsconf-mail-prepare-behavior (if arg t emacsconf-mail-prepare-behavior)))
+ (emacsconf-mail-prepare
+ '(:subject "${conf-name} ${year} acceptance${plural}: ${titles}"
+ :cc "emacsconf-submit@gnu.org"
+ :reply-to "emacsconf-submit@gnu.org, ${email}, ${user-email}"
+ :mail-followup-to "emacsconf-submit@gnu.org, ${email}, ${user-email}"
+ :body
+ "
+Hi, ${speakers-short}!
+
+Looks like all systems are a go for your talk${plural}. Thanks!
+
+${info}
+Please feel free to update the wiki with more information or e-mail us
+if you'd like help with any changes.${fill}
+
+If you want to get started on your talk early, we have some
+instructions at ${base}${year}/prepare/ that might help.
+We strongly encourage speakers to prepare talk videos by
+${video-target-date} in order to reduce technical risks and make
+things flow more smoothly. Plus, we might be able to get it captioned
+by volunteers, just like the talks last year.${wrap}
+
+Don't sweat it if you're a few minutes over or under your proposed
+time${plural}. If it looks like a much shorter or longer talk once you
+start getting into it, let us know and we might be able to
+adjust.${wrap}
+
+${todos}
+I'll follow up with the specific schedule for your talk once things
+settle down. In the meantime, please let us know if you have any
+questions or if there's anything we can do to help out!
+
+${signature}"
+ :log-note "accepted talk")
+ (plist-get (cadr group) :email)
+ (list
+ :base emacsconf-base-url
+ :user-email user-mail-address
+ :year emacsconf-year
+ :signature user-full-name
+ :conf-name emacsconf-name
+ :email (plist-get (cadr group) :email)
+ :titles (mapconcat (lambda (o) (plist-get o :title)) (cdr group) "; ")
+ :info
+ (mapconcat
+ (lambda (o)
+ (emacsconf-replace-plist-in-string
+ (list :title (plist-get o :title)
+ :url (concat emacsconf-base-url (plist-get o :url))
+ :time (plist-get o :time))
+ " ${title}\n ${url}\n ${time} minutes\n"))
+ (cdr group)
+ "\n")
+ :todos
+ (concat
+ (if (= 1 (length (cdr group))) "Here's a handy TODO you can use if you want:" "Here are handy TODOs you can use if you want:")
+ "\n\n"
+ (mapconcat
+ (lambda (o)
+ (emacsconf-replace-plist-in-string
+ (list :title (plist-get o :title)
+ :conf-name emacsconf-name
+ :year emacsconf-year
+ :video-target-date (format-time-string "%Y-%m-%d %a" (date-to-time emacsconf-video-target-date))
+ :submit-email emacsconf-submit-email
+ :base emacsconf-base-url
+ :q-and-a (if (emacsconf-schedule-q-and-a-p o) " (+ time afterwards for Q&A)" "")
+ :time (plist-get o :time)
+ :url (plist-get o :url))
+ "** TODO Prepare \"${title}\" for ${conf-name} ${year}
+ DEADLINE: <${video-target-date}>
+ (feel free to send it in earlier; let us know at ${submit-email} you're running late)
+ Reserved time: ${time} minutes${q-and-a}
+ Instructions: ${base}${year}/prepare/
+ Talk page: ${base}${url}
+ (remember to use large text for your video!)
+"))
+ (cdr group) "\n\n"))
+ :plural (if (= 1 (length (cdr group))) "" "s")
+ :video-target-date (format-time-string "%Y-%m-%d %a" (date-to-time emacsconf-video-target-date))
+ :speakers-short (plist-get (cadr group) :speakers-short)))))
+
(defun emacsconf-mail-accept-talk (talk)
"Send acceptance letter."
(interactive (list (emacsconf-complete-talk-info)))
@@ -651,23 +784,23 @@ questions or if there's anything we can do to help out!
${signature}"
:function emacsconf-mail-accept-talk
:log-note "accepted talk")
- (plist-get talk :email)
- (list
- :base emacsconf-base-url
- :user-email user-mail-address
- :year emacsconf-year
- :signature user-full-name
- :conf-name emacsconf-name
- :title (plist-get talk :title)
- :email (plist-get talk :email)
- :time (plist-get talk :time)
- :speakers-short (plist-get talk :speakers-short)
- :url (concat emacsconf-base-url (plist-get talk :url))
- :video-target-date emacsconf-video-target-date)))
+ (plist-get talk :email)
+ (list
+ :base emacsconf-base-url
+ :user-email user-mail-address
+ :year emacsconf-year
+ :signature user-full-name
+ :conf-name emacsconf-name
+ :title (plist-get talk :title)
+ :email (plist-get talk :email)
+ :time (plist-get talk :time)
+ :speakers-short (plist-get talk :speakers-short)
+ :url (concat emacsconf-base-url (plist-get talk :url))
+ :video-target-date emacsconf-video-target-date)))
(defvar emacsconf-mail-bcc-email "*Extra e-mail address to Bcc for delivery confirmation.")
-(defun emacsconf-mail-format-talk-schedule (o)
+(defun emacsconf-mail-format-talk-schedule (o &optional old-schedule)
"Format the schedule for O for inclusion in mail messages etc."
(interactive (list (emacsconf-complete-talk)))
(when (stringp o)
@@ -682,6 +815,8 @@ ${signature}"
(plist-get o :start-time)
(plist-get o :timezone)
"%b %-e %a %-I:%M %#p %Z"))))
+ (when old-schedule
+ (setq result (format "%s\n(Previous: %s)" result old-schedule)))
(when (called-interactively-p 'any)
(insert result))
result))
@@ -714,13 +849,14 @@ comments or requests.
You can see the draft schedule at
${base}${year}/organizers-notebook/?highlight=${slugs}#draft-schedule
. If you use a Javascript-enabled browser, your talk${plural}
-will be highlighted with a black border in the schedule, and your
-talk ID${plural} (${slugs}) will be highlighted with a yellow
-background in the notes that follow.${wrap}
+will be highlighted with a thicker black border in the schedule. If I've mentioned your
+talk ID${plural} (${slugs}) in the notes that follow, they'll be highlighted with a yellow
+background.${wrap}
As of the time I write this e-mail, your tentative schedule is:
${schedule}
+
${availability-note}${timezone-note}I might also be able to move things around if you want to
attend any conflicting Q&A sessions or if your availability
changes.${wrap}
@@ -733,11 +869,11 @@ the updated schedule along with check-in instructions before the
conference.${wrap}
We plan to announce the schedule to the general public on
-${schedule-announcement-date}, so we'd love to incorporate any
-schedule feedback before then.${wrap}
+${schedule-announcement-date}, so we'd love to incorporate any schedule
+feedback before then. Does this schedule work for you, or would something else be better?${wrap}
-In the meantime, good luck working on your presentation. ${todos}
-Looking forward to ${conf-name} with you!
+In the meantime, good luck working on your presentation${plural}.
+Feel free to reach out if you have any questions. Looking forward to ${conf-name} with you!${wrap}
${signature}
"
@@ -751,17 +887,20 @@ so that's totally all right. You don't have to make it to the
time your talk is scheduled; this e-mail is just to keep you up
to date. =)
+${schedule}
+
You can see the draft schedule at
${base}${year}/organizers-notebook/?highlight=${slugs}#draft-schedule
. If you use a Javascript-enabled browser, your talk${plural}
-will have a black border in the schedule and a yellow background
-in the notes that follow.
+will be highlighted with a thicker black border in the schedule. If I've mentioned your
+talk ID${plural} (${slugs}) in the notes that follow, they'll be highlighted with a yellow
+background.${wrap}
We'll also update the schedule as we get closer to the
conference, but I'll let you know if things change a lot. Anyway,
that's how things are shaping up.
-In the meantime, good luck working on your presentation. ${todos}
+In the meantime, good luck working on your presentation${plural}.
Looking forward to ${conf-name} with you!
${signature}
@@ -778,33 +917,14 @@ ${signature}
:conf-name emacsconf-name
:speakers-short (plist-get (cadr group) :speakers-short)
:plural (if (= 1 (length (cdr group))) "" "s")
- :todos
- (concat
- (if (= 1 (length (cdr group))) "Here's a handy TODO you can use if you want:" "Here are handy TODOs you can use if you want:")
- "\n\n"
- (mapconcat
- (lambda (o)
- (emacsconf-replace-plist-in-string
- (list :title (plist-get o :title)
- :conf-name emacsconf-name
- :year emacsconf-year
- :video-target-date (format-time-string "%Y-%m-%d %a" (date-to-time emacsconf-video-target-date))
- :submit-email emacsconf-submit-email
- :base emacsconf-base-url
- :q-and-a (if (emacsconf-schedule-q-and-a-p o) " (+ time afterwards for Q&A)" "")
- :time (plist-get o :time)
- :url (plist-get o :url))
- "** TODO Prepare \"${title}\" for ${conf-name} ${year}
- DEADLINE: <${video-target-date}>
- (feel free to send it in earlier; let us know at ${submit-email} you're running late)
- Reserved time: ${time} minutes${q-and-a}
- Instructions: ${base}${year}/prepare/
- Talk page: ${base}${url}
-"))
- (cdr group) "\n\n"))
:email-notes (emacsconf-surround "ZZZ: " (plist-get (cadr group) :email-notes) "\n\n" "")
:schedule
- (emacsconf-indent-string (mapconcat #'emacsconf-mail-format-talk-schedule (cdr group) "\n") 2)
+ (emacsconf-indent-string (mapconcat #'emacsconf-mail-format-talk-schedule
+ (sort (cdr group)
+ :key (lambda (o) (plist-get o :start-time))
+ :lessp #'time-less-p)
+ "\n\n")
+ 2)
:availability-note
(if (delq nil (emacsconf-schedule-get-time-constraint (cadr group)))
(emacsconf-replace-plist-in-string
@@ -817,16 +937,17 @@ ${signature}
(append
(list :renamed-timezone (emacsconf-schedule-rename-etc-timezone (plist-get (cadr group) :timezone)))
(cadr group))
- "Just let me know if you want us to use a different timezone for translating times in future e-mails. ")
- "I don't think I have a timezone noted for you yet. If you want, I can translate times into your local timezone for you in future e-mails. Just let me know what you would like. ")))))
+ "Just let me know if you want us to use a different time zone for translating times in future e-mails. ")
+ "I don't think I have a time zone noted for you yet. If you want, I can translate times into your local time zone for you in future e-mails. Just let me know what you would like. ")))))
(defun emacsconf-mail-acknowledge-upload (talk)
"Acknowledge uploaded files for TALK."
(interactive (list (emacsconf-complete-talk-info)))
(emacsconf-mail-prepare
'(:subject "${conf-name} ${year}: received uploaded file${plural} for ${title}"
- :cc "emacsconf-submit@gnu.org"
+ ;; :cc "emacsconf-submit@gnu.org"
:reply-to "emacsconf-submit@gnu.org, ${email}, ${user-email}"
+ :log-note "acknowledged submission"
:mail-followup-to "emacsconf-submit@gnu.org, ${email}, ${user-email}"
:body
"Hi, ${speakers-short}!
@@ -836,12 +957,12 @@ Now we have the following file${plural} starting with ${file-prefix}:
${file-list}
-I've added the video to the processing queue. You can see how
-things are going backstage at ${backstage-url-with-auth} . We'll
-be working on captioning your talk over the next few weeks. We'll
-e-mail again a little closer to the conference with schedule
-updates and other useful information. If you want to upload a new
-version, you can upload it the same way you did the previous
+I've added the video to the processing queue. You can see how things are
+going backstage at ${backstage-url-with-auth} . I or another captioning
+volunteer will work on captioning your talk over the next few weeks.
+We'll e-mail again a little closer to the conference with
+schedule updates and other useful information. If you want to upload a
+new version, you can upload it the same way you did the previous
one.${fill}
Please feel free to e-mail us at ${submit-email} if you need help updating the talk wiki page at ${base}${url} or if you have other questions.
@@ -902,6 +1023,7 @@ ${signature}")
files
"\n")))))
+;;;###autoload
(defun emacsconf-mail-captions-for-review (talk)
"E-mail captions for TALK so that the speakers can review them."
(interactive (list (emacsconf-complete-talk-info
@@ -910,16 +1032,25 @@ ${signature}")
(and (emacsconf-talk-file talk "--main.vtt")
(file-exists-p (emacsconf-talk-file talk "--main.vtt"))))
(emacsconf-get-talk-info)))))
+ (unless (plist-get talk :captioner)
+ (message "No captioner set for talk.")
+ (save-excursion
+ (emacsconf-with-talk-heading talk
+ (org-set-property "CAPTIONER" (assoc-default "CUSTOM_ID" (emacsconf-complete-volunteer)
+ #'string=)))
+ (setq talk (emacsconf-search-talk-info (plist-get talk :slug)))))
(let ((captions (expand-file-name (concat (plist-get talk :file-prefix) "--main.vtt")
emacsconf-cache-dir))
(captioner-info
(with-current-buffer (find-file-noselect emacsconf-org-file)
(org-entry-properties (org-find-property "CUSTOM_ID" (plist-get talk :captioner))))))
+
(emacsconf-mail-prepare
(list
:subject "${conf-name} ${year}: Captions for ${title}"
:to "${email}"
:cc "${captioner-email}"
+ :log-note "sent captions for review"
:body "${email-notes}Hi ${speakers-short}!
Because you sent in your video before the conference, we were able to
@@ -971,6 +1102,60 @@ ${captions}
:captions (mapconcat (lambda (sub) (concat (emacsconf-surround "\nNOTE " (elt sub 4) "\n\n" "") (elt sub 3))) (subed-parse-file captions) "\n")))
(mml-attach-file captions "text/vtt" "Subtitles" "attachment")))
+(defun emacsconf-mail-answers-for-review (talk)
+ "E-mail Q&A session for TALK so that the speakers can review them."
+ (interactive (list (emacsconf-complete-talk-info
+ (seq-filter
+ (lambda (talk)
+ (and (emacsconf-talk-file talk "--answers.vtt")
+ (file-exists-p (emacsconf-talk-file talk "--answers.vtt"))
+ (not (plist-get talk :qa-public))))
+ (emacsconf-get-talk-info)))))
+ (let ((captions (expand-file-name (concat (plist-get talk :file-prefix) "--answers.vtt")
+ emacsconf-cache-dir))
+ (captioner-info
+ (with-current-buffer (find-file-noselect emacsconf-org-file)
+ (org-entry-properties (org-find-property "CUSTOM_ID" (plist-get talk :captioner))))))
+ (emacsconf-mail-prepare
+ (list
+ :subject "${conf-name} ${year}: Q&A for ${title}"
+ :to "${email}"
+ :log-note "sent q&a for review"
+ :body "${email-notes}Hi ${speakers-short}!
+
+Thank you for speaking at ${conf-name} ${year}! We're working on getting
+the Q&A recordings out the door. We noticed you had a long Q&A session
+that continued off-stream. Was there anything that would need to be
+removed before we can publish the recording? You can review it at ${url}
+(video is in --answers.webm, captions are in --answers.vtt). I've also
+attached the automatic captions for easy skimming. In the interests of
+getting stuff out the door quickly, we haven't edited the Q&A captions
+much; it's mostly there so you can remember the conversation and let us
+know if we need to trim anything.${wrap}
+
+${signature}
+
+${captions}
+")
+ (plist-get talk :email)
+ (list
+ :email-notes (emacsconf-surround "ZZZ: " (plist-get talk :email-notes) "\n\n" "")
+ :conf-name emacsconf-name
+ :speakers-short (plist-get talk :speakers-short)
+ :year emacsconf-year
+ :email (plist-get talk :email)
+ :title (plist-get talk :title)
+ :signature user-full-name
+ :url
+ (format "https://%s:%s@media.emacsconf.org/%s/backstage/#%s"
+ emacsconf-backstage-user
+ emacsconf-backstage-password
+ emacsconf-year
+ (plist-get talk :slug))
+ :password emacsconf-backstage-password
+ :captions (mapconcat (lambda (sub) (concat (emacsconf-surround "\nNOTE " (elt sub 4) "\n\n" "") (elt sub 3))) (subed-parse-file captions) "\n")))
+ (mml-attach-file captions "text/vtt" "Subtitles" "attachment")))
+
(defun emacsconf-mail-upload-and-backstage-info (group)
"E-mail upload and backstage access information to GROUP."
(interactive (list (emacsconf-mail-complete-email-group)))
@@ -979,7 +1164,7 @@ ${captions}
:subject "${conf-name} ${year}: Upload instructions, backstage info"
:reply-to "emacsconf-submit@gnu.org, ${email}, ${user-email}"
:mail-followup-to "emacsconf-submit@gnu.org, ${email}, ${user-email}"
- :log-note "sent backstage and upload information"
+ :log-note "emacsconf-mail-upload-and-backstage-info"
:body
"${email-notes}Hi ${name}!
@@ -1021,7 +1206,9 @@ ${signature}")
:backstage-user emacsconf-backstage-user
:backstage-password emacsconf-backstage-password
:upload-url
- (concat "https://ftp-upload.emacsconf.org/?sid="
+ (concat "https://upload.emacsconf.org/?sid="
+ emacsconf-year
+ "-"
emacsconf-upload-password
"-"
(mapconcat (lambda (o) (plist-get o :slug)) (cdr group) "-"))
@@ -1114,10 +1301,7 @@ people's talks too."))
(defun emacsconf-mail-backstage-info-to-captioning-volunteers ()
"E-mail backstage info to captioning volunteers."
(interactive)
- (dolist (volunteer (seq-filter
- (lambda (o)
- (string-match ":captions:" (assoc-default "ALLTAGS" o 'string= "")))
- (emacsconf-get-volunteer-info)))
+ (dolist (volunteer (emacsconf-get-volunteer-info "caption"))
(emacsconf-mail-backstage-info
(list
(assoc-default "EMAIL" volunteer)
@@ -1126,6 +1310,48 @@ people's talks too."))
(or (assoc-default "NAME" volunteer)
(assoc-default "NAME_SHORT" volunteer)))))))
+(defun emacsconf-mail-last-minute-activation ()
+ "E-mail backstage info to captioning volunteers."
+ (interactive)
+ (dolist (volunteer (emacsconf-get-volunteer-info "lastmin"))
+ (emacsconf-mail-prepare
+ (list
+ :subject "${conf-name} ${conf-year}: Finishing touches! =)"
+ :reply-to "emacsconf-submit@gnu.org, ${email}, ${user-email}"
+ :mail-followup-to "emacsconf-submit@gnu.org, ${email}, ${user-email}"
+ :body
+ "Hello, ${name-short}!
+
+Home stretch! ${conf-name} ${conf-year} is coming up this weekend.
+It looks like we're in pretty good shape, and I'm excited about how things will go.
+
+If you happen to find yourself with some extra time, we'd love it if you
+could check out the backstage area for whichever talks you're interested
+in. Here's the URL:
+
+ ${backstage-url}
+
+You could watch a talk or two (or more!), see if there are any
+video/audio/caption glitches, add a couple of questions or notes to the
+Etherpad to get the ball rolling... Whatever you think might help make
+${conf-name} smoother and more fun!
+
+No worries if you're busy. =)
+
+Thanks for being part of ${conf-name} ${conf-year}!
+
+${user-signature}")
+ (assoc-default "EMAIL" volunteer)
+ (list
+ :backstage-url (emacsconf-backstage-url)
+ :conf-name emacsconf-name
+ :conf-year emacsconf-year
+ :email (assoc-default "EMAIL" volunteer)
+ :user-email user-mail-address
+ :user-signature user-full-name
+ :name-short (or (assoc-default "NAME" volunteer)
+ (assoc-default "NAME_SHORT" volunteer))))))
+
(defun emacsconf-mail-backstage-info-to-speakers-and-captioners ()
(interactive)
(let ((template (emacsconf-mail-merge-get-template "backstage"))
@@ -1165,14 +1391,19 @@ quality of the reencoded video."))
speaker-groups))
volunteer-groups)))))
-(defun emacsconf-mail-bbb-tips-and-intro-to-all (&optional types)
- "Draft BBB information for all speakers."
+(defun emacsconf-mail-intro-to-all (&optional types)
+ "Ask speakers to review intros and let them know that check-in info will come soon."
(interactive)
- (let* ((log-note "sent bbb-tips-and-intro")
+ (let* ((log-note "sent intro")
+ (talks (emacsconf-filter-talks-by-logbook
+ log-note
+ (emacsconf-active-talks (emacsconf-get-talk-info))))
(talk-groups (seq-group-by
(lambda (talk)
(cond
- ((string= (plist-get talk :status) "WAITING_FOR_PREREC")
+ ((plist-get talk :live) 'live-talk)
+ ((member (plist-get talk :status)
+ (list "WAITING_FOR_PREREC" "TO_CONFIRM"))
'waiting-for-prerec)
((string= (plist-get talk :qa-type) "live")
'live)
@@ -1183,62 +1414,91 @@ quality of the reencoded video."))
'after)
(t (error "Exception for %s" (plist-get talk :slug)))))
(seq-filter (lambda (o) (plist-get o :email))
- (emacsconf-filter-talks-by-logbook
- log-note
- (emacsconf-active-talks (emacsconf-get-talk-info))))))
+ talks)))
(base (list :reply-to "emacsconf-submit@gnu.org, ${email}, ${user-email}"
:log-note log-note
:mail-followup-to "emacsconf-submit@gnu.org, ${email}, ${user-email}")))
(setq types (or types (mapcar 'car talk-groups)))
(dolist (type types)
- (dolist (group (emacsconf-mail-groups (cdr (assoc-default type talk-groups))))
- (emacsconf-mail-prepare
- (append
- base
- (pcase type
- ('waiting-for-prerec
- (list
- :subject "${conf-name} ${year} action needed: Options, BigBlueButton info, intro"
- :body
- "${email-notes}Hi, ${name}!
+ (when (assoc-default type talk-groups)
+ (dolist (group (emacsconf-mail-groups (assoc-default type talk-groups)))
+ (emacsconf-mail-prepare
+ (append
+ base
+ (pcase type
+ ('live-talk
+ (list
+ :subject "${conf-name} ${year}: confirming with you, also please check intro pronunciation"
+ :body
+ "${email-notes}Hi, ${name}!
+
+How are you feeling about ${conf-name}? It's coming up soon - just two weeks away.
+I wanted to check in with all the speakers in case things have come up.
+I have you penciled in for a live talk. In case life
+has become unexpectedly busy for you and you're not sure if
+you want to do it, let me know and I can adjust the schedule.
+No stress. =) ${fill}
+
+Since you're planning to do a live talk, I'll set up a BigBlueButton web
+conference room. I plan to bring the web conference server up on Friday
+night before the conference, and I'll e-mail you the check-in
+information then so that you can test things if you like. If you want to
+test things beforehand, let me know what time and date might work for
+you and I can set that up.${fill}
+
+Also, could you please take a minute to check if I pronounce your name
+correctly in the intro I recorded? The recording is at ${intro-url} ,
+and you can also find it under \"--intro.webm\" in your talk's section
+at ${backstage-url-with-credentials} . If it needs tweaking, you can
+upload a recording to ${upload-url} (password ${upload-password}) or
+e-mail me the corrections.
+
+Best regards,
-${conf-name} is in two weeks. Aaah! I don't think we have your
-presentation yet, but I'm not panicking (much) because we've got plans
-and backup plans.
+${signature}"))
+ ('waiting-for-prerec
+ (list
+ :subject "${conf-name} ${year}: gently checking in with you, also please check intro"
+ :body
+ "${email-notes}Hi, ${name}!
+
+How are you feeling about ${conf-name}? It's coming up soon - just two weeks away.
+I wanted to check in with all the speakers in case things have come up.
+In case life has become unexpectedly busy for you and you're not sure if
+you want to go through with your presentation, let me know and I can adjust the schedule.
+No stress. =) ${fill}
Option A: You can upload your presentation before the conference
-There's still a little time to squeeze in processing presentations and
-possibly even captioning them. If you're getting stuck because you
-want your presentation to be totally awesome, it's okay, it doesn't
-have to be perfect. If it's too long or too short, that's cool too; we
-can manage the time around that. We'll figure things out together. =)
+There might still be a little time to squeeze in processing
+presentations and possibly even captioning them. If you're getting stuck
+because you want your presentation to be totally awesome, it's okay, it
+doesn't have to be perfect. If it's too long or too short, that's cool
+too; we can manage the time around that. We'll figure things out
+together. =)
You can upload your file(s) to ${upload-url} (password
${upload-password}) and we'll get things going.${fill}
-If you're thinking of doing the Q&A in a live web conference,
-please also test the BigBlueButton URL described under Option
-B. Thanks!
-
Option B: You can do it live
Sometimes it's easier to do a presentation live, or sometimes you're
-making last-minute tweaks and want to play the latest copy of your
-video from your own computer. We can do the presentation live over
-BigBlueButton. Your room URL is ${bbb-url} , and I've put together
-some tips at ${bbb-tips} . Please
-help reduce the technical risks by trying out the BigBlueButton setup
-before November 28 so that there's time to help troubleshoot.${fill}
+making last-minute tweaks and want to play the latest copy of your video
+from your own computer. We can do the presentation live over
+BigBlueButton. I plan to bring the web conference server up on Friday
+night before the conference, and I'll e-mail you the check-in
+information then so that you can test things if you like. If you want to
+test things beforehand, let me know what time and date might work for
+you and I can set that up.${fill}
Option C: It's okay, you can cancel
-Sometimes an interesting talk idea turns out to be more challenging
-than you'd like. Sometimes life happens. If you're stressing out and
-you don't think you can make it, no worries, no need to feel
-embarrassed or guilty or anything like that. Let me know and I can
-update the schedule so that other speakers have extra time for Q&A. We
-hope you'll consider proposing a talk for another EmacsConf!
+Sometimes an interesting talk idea turns out to be more challenging than
+you'd like. Sometimes life happens. If you're stressing out and you
+don't think you can make it, no worries, no need to feel embarrassed or
+guilty or anything like that. Let me know and I can update the
+schedule. We hope you'll consider proposing a talk for another
+EmacsConf!
----------------
If you're planning to go through with the talk (yay!), could you
@@ -1252,21 +1512,25 @@ e-mail me the corrections.${fill}
Best regards,
${signature}"))
- ('live
- (list
- :subject "${conf-name} ${year} action needed: BigBlueButton info, intro"
- :body
- "${email-notes}Hi, ${name}!
-
-Thanks again for uploading your presentation early!
-
-Since you're planning to do a live Q&A session, I've set up a
-BigBlueButton web conference room for you at ${bbb-url} . I've
-also put together some tips at ${bbb-tips} . Please help reduce
-the technical risks by trying out the BigBlueButton setup before
-November 28 so that there's time to help troubleshoot. Sharing
-system audio or multi-monitor setups can sometimes be tricky, so
-please let us know if you need help figuring things out. ${fill}
+ ('live
+ (list
+ :subject "${conf-name} ${year}: thanks again for your video, also please check intro pronunciation"
+ :body
+ "${email-notes}Hi, ${name}!
+
+Thanks again for uploading your presentation early! How are you feeling
+about ${conf-name}? It's coming up soon - just two weeks away. I wanted
+to check in with all the speakers in case things have come up. In case
+life has become unexpectedly busy for you and you're not sure if you
+want to go through with your presentation & live Q&A, let me know and I
+can adjust the schedule. No stress. =) ${fill}
+
+Since you're planning to do a live Q&A session, I'll set up a
+BigBlueButton web conference room. I plan to bring the web conference
+server up on Friday night before the conference, and I'll e-mail you the
+check-in information then so that you can test things if you like. If
+you want to test things beforehand, let me know what time and date might
+work for you and I can set that up.${fill}
Also, could you please take a minute to check if I pronounce your name
correctly in the intro I recorded? The recording is at ${intro-url} ,
@@ -1278,14 +1542,20 @@ e-mail me the corrections.
Best regards,
${signature}"))
- ('pad-or-irc
- (list
- :subject "${conf-name} ${year} action needed: check intro pronunciation"
- :body
- "${email-notes}Hi, ${name}!
+ ('pad-or-irc
+ (list
+ :subject "${conf-name} ${year}: thanks again for your video, also please check intro pronunciation"
+ :body
+ "${email-notes}Hi, ${name}!
Thanks again for uploading your presentation early!
+How are you feeling about ${conf-name}? It's coming up soon - just two
+weeks away. I wanted to check in with all the speakers in case things
+have come up. In case life has become unexpectedly busy for you and
+you're not sure if you want to go through with your presentation &
+Q&A, let me know and I can adjust the schedule. No stress. =)${fill}
+
Could you please take a minute to check if I pronounce your name
correctly in the intro I recorded? The recording is at ${intro-url} ,
and you can also find it under \"--intro.webm\" in your talk's section
@@ -1293,17 +1563,17 @@ at ${backstage-url-with-credentials} . If it needs tweaking, you can
upload a recording to ${upload-url} (password ${upload-password}) or
e-mail me the corrections.
-We'll send you check-in instructions a few days before the conference
+We'll send you check-in instructions on the Friday before the conference
so that you'll be all set.
Best regards,
${signature}"))
- ('after
- (list
- :subject "${conf-name} ${year} action needed: check intro pronunciation"
- :body
- "${email-notes}Hi, ${name}!
+ ('after
+ (list
+ :subject "${conf-name} ${year}: thanks again for your video, please check intro pronunciation"
+ :body
+ "${email-notes}Hi, ${name}!
Thanks again for uploading your presentation early!
@@ -1324,58 +1594,60 @@ us!
Best regards,
${signature}"))
- (_ (error "Unknown type %s" (symbol-name type)))))
- (car group)
- (list
- :email-notes (or (plist-get (car group) :email-notes) "")
- :conf-name emacsconf-name
- :year emacsconf-year
- :name (plist-get (cadr group) :speakers-short)
- :email (car group)
- :user-email user-mail-address
- :signature user-full-name
- :backstage-url-with-credentials
- (mapconcat (lambda (talk)
- (format "https://%s:%s@media.emacsconf.org/%s/backstage/#%s"
- emacsconf-backstage-user
- emacsconf-backstage-password
- emacsconf-year
- (plist-get talk :slug)))
- (cdr group)
- " , ")
- :upload-url (concat "https://ftp-upload.emacsconf.org/?sid="
- emacsconf-upload-password
- "-"
- (mapconcat (lambda (o) (plist-get o :slug)) (cdr group) "-"))
- :upload-password emacsconf-upload-password
- :bbb-url
- (cond
- ((null (file-exists-p
- (expand-file-name
- (format "assets/redirects/open/bbb-%s.html" (plist-get (cadr group) :slug))
- emacsconf-backstage-dir)))
- (error "Backstage redirect for %s does not exist" (plist-get (cadr group) :slug)))
- ((null (= (length (seq-uniq (mapcar (lambda (o) (plist-get o :bbb-room)) (cdr group)))) 1))
- (error "Number of rooms for %s speaker: %d"
- (plist-get (cadr group) :slug)
- (length (seq-uniq (mapcar (lambda (o) (plist-get o :bbb-room)) (cdr group))))))
- (t (emacsconf-backstage-url (plist-get (car (cdr group)) :bbb-backstage))))
- :bbb-tips (concat emacsconf-base-url emacsconf-year "/bbb-for-speakers/")
- :intro-url (mapconcat
- (lambda (talk)
- (if (file-exists-p (expand-file-name
- (concat (plist-get talk :file-prefix) "--intro.webm")
- emacsconf-backstage-dir))
- (format "https://%s:%s@media.emacsconf.org/%s/backstage/%s--intro.webm"
- emacsconf-backstage-user
- emacsconf-backstage-password
- emacsconf-year
- (plist-get talk :file-prefix))
- (error "No intro file for %s" (plist-get talk :slug))))
- (cdr group)
- " , ")))))))
-
-(defun emacsconf-mail-checkin-instructions-to-all ()
+ (_ (error "Unknown type %s" (symbol-name type)))))
+ (car group)
+ (list
+ :email-notes (or (plist-get (car group) :email-notes) "")
+ :conf-name emacsconf-name
+ :year emacsconf-year
+ :name (plist-get (cadr group) :speakers-short)
+ :email (car group)
+ :user-email user-mail-address
+ :signature user-full-name
+ :backstage-url-with-credentials
+ (mapconcat (lambda (talk)
+ (format "https://%s:%s@media.emacsconf.org/%s/backstage/#%s"
+ emacsconf-backstage-user
+ emacsconf-backstage-password
+ emacsconf-year
+ (plist-get talk :slug)))
+ (cdr group)
+ " , ")
+ :upload-url (concat "https://upload.emacsconf.org/?sid="
+ emacsconf-upload-password
+ "-"
+ (mapconcat (lambda (o) (plist-get o :slug)) (cdr group) "-"))
+ :upload-password emacsconf-upload-password
+ ;; :bbb-url
+ ;; (cond
+ ;; ((string= (plist-get (cadr group) :qa-type) "none")
+ ;; "{ZZZ: No BBB URL because Q&A is after the conference}")
+ ;; ((null (file-exists-p
+ ;; (expand-file-name
+ ;; (format "assets/redirects/open/bbb-%s.html" (plist-get (cadr group) :slug))
+ ;; emacsconf-backstage-dir)))
+ ;; (error "Backstage redirect for %s does not exist" (plist-get (cadr group) :slug)))
+ ;; ((null (= (length (seq-uniq (mapcar (lambda (o) (plist-get o :bbb-room)) (cdr group)))) 1))
+ ;; (error "Number of rooms for %s speaker: %d"
+ ;; (plist-get (cadr group) :slug)
+ ;; (length (seq-uniq (mapcar (lambda (o) (plist-get o :bbb-room)) (cdr group))))))
+ ;; (t (emacsconf-backstage-url (plist-get (car (cdr group)) :bbb-backstage))))
+ ;; :bbb-tips (concat emacsconf-base-url emacsconf-year "/bbb-for-speakers/")
+ :intro-url (mapconcat
+ (lambda (talk)
+ (if (file-exists-p (expand-file-name
+ (emacsconf-talk-file talk "--intro.webm")
+ emacsconf-backstage-dir))
+ (format "https://%s:%s@media.emacsconf.org/%s/backstage/%s--intro.webm"
+ emacsconf-backstage-user
+ emacsconf-backstage-password
+ emacsconf-year
+ (plist-get talk :file-prefix))
+ (error "No intro file for %s" (plist-get talk :slug))))
+ (cdr group)
+ " , "))))))))
+
+(defun emacsconf-mail-checkin-instructions-to-all (&optional info)
"Draft check-in instructions for all speakers."
(interactive)
(let* ((talks
@@ -1383,27 +1655,96 @@ ${signature}"))
"sent check-in information"
(seq-filter (lambda (o) (and (plist-get o :email)
(plist-get o :q-and-a)))
- (emacsconf-publish-prepare-for-display (emacsconf-get-talk-info)))))
- (by-attendance (seq-group-by (lambda (o) (null (string-match "after" (plist-get o :q-and-a))))
+ (emacsconf-publish-prepare-for-display (or info (emacsconf-get-talk-info))))))
+ (by-attendance (seq-group-by (lambda (o) (null (string-match "after\\|none" (plist-get o :qa-type))))
talks)))
- (dolist (group (emacsconf-mail-groups (assoc-default nil by-attendance)))
- (emacsconf-mail-checkin-instructions-for-nonattending-speakers group))
+ (when (assoc-default nil by-attendance)
+ (dolist (group (emacsconf-mail-groups (assoc-default nil by-attendance)))
+ (emacsconf-mail-checkin-instructions-for-nonattending-speakers group)))
(dolist (group (emacsconf-mail-groups (assoc-default t by-attendance)))
(emacsconf-mail-checkin-instructions-for-attending-speakers group))))
-(defun emacsconf-mail-schedule-update ()
+(defun emacsconf-mail-schedule-updates ()
+ "E-mail general schedule updates."
+ (interactive)
+ (let* ((log-note (concat "sent schedule as of " (format-time-string "%Y-%m-%d")))
+ (talks (emacsconf-filter-talks-by-logbook
+ log-note
+ (emacsconf-rescheduled-talks)))
+ (groups (emacsconf-mail-groups talks)))
+ (dolist (group groups)
+ (emacsconf-mail-interim-schedule-update group log-note))))
+
+(defun emacsconf-mail-interim-schedule-update (group &optional log-note)
+ "E-mail a quick update about the schedule."
+ (interactive (list (emacsconf-complete-talk-info)))
+ (emacsconf-mail-prepare
+ (list
+ :subject "${conf-name} ${year}: Schedule update as of ${date}: ${summary}"
+ :reply-to "emacsconf-submit@gnu.org, ${email}, ${user-email}"
+ :mail-followup-to "emacsconf-submit@gnu.org, ${email}, ${user-email}"
+ :log-note log-note
+ :body
+ "Hello, ${speakers-short}!
+
+We tweaked the schedule a bit so that it's based on the current video lengths.
+Your new schedule is:
+
+${schedule}
+
+Let us know if you need to reschedule!
+
+${signature}")
+ (or (plist-get group :email)
+ (plist-get (car (cdr group)) :email))
+ (list
+ :year emacsconf-year
+ :base-url emacsconf-base-url
+ :conf-name emacsconf-name
+ :user-email user-mail-address
+ :date (format-time-string "%Y-%m-%d")
+ :email (or (plist-get group :email)
+ (plist-get (car (cdr group)) :email))
+ :speakers-short
+ (or
+ (plist-get group :speakers-short)
+ (plist-get (car (cdr group)) :speakers-short))
+ :signature user-full-name
+ :summary
+ (mapconcat
+ (lambda (talk)
+ (let ((minutes (emacsconf-schedule-difference-from-emailed talk)))
+ (if (> (abs minutes) 0)
+ (format "%s: %s min %s"
+ (plist-get talk :slug)
+ (abs minutes)
+ (if (< minutes 0)
+ "earlier"
+ "later"))
+ "same time, different length")))
+ (if (plist-get group :slug)
+ (list group)
+ (cdr group))
+ "; ")
+ :schedule
+ (mapconcat (lambda (talk)
+ (emacsconf-indent-string (emacsconf-mail-format-talk-schedule talk (plist-get talk :emailed-schedule)) 2))
+ (if (plist-get group :slug)
+ (list group)
+ (cdr group))
+ "\n\n"))))
+
+(defun emacsconf-mail-check-in-update (talk-or-group)
"E-mail day-of schedule updates"
- (interactive)
+ (interactive (list (emacsconf-complete-talk-info)))
(let ((groups
- (emacsconf-mail-groups
- (seq-remove (lambda (talk)
- (string= (replace-regexp-in-string "[<>]" "" (plist-get talk :scheduled))
- (plist-get talk :checkin-schedule-sent)))
- (emacsconf-publish-prepare-for-display (emacsconf-get-talk-info))))))
+ (emacsconf-mail-groups (if (plist-get talk-or-group :slug)
+ (list talk-or-group)
+ talk-or-group))))
(dolist (group groups)
(emacsconf-mail-prepare
(list
- :subject "${conf-name} ${year}: Schedule update - new check-in time ${checkin-time}"
+ :subject "${conf-name} ${year}: SCHEDULE UPDATE - new check-in time ${checkin-time}"
:reply-to "emacsconf-submit@gnu.org, ${email}, ${user-email}"
:mail-followup-to "emacsconf-submit@gnu.org, ${email}, ${user-email}"
:log-note "sent updated schedule"
@@ -1419,8 +1760,7 @@ or technical issues, and so that we don't worry too much about
missing speakers (aaah!). You can find the check-in
process at ${base-url}${year}/speakers/ . ${wrap}
-If something comes up, please let us know as soon as you can. Here's
-my emergency contact information: ${emergency}${wrap}
+If something comes up, please let us know as soon as you can.
Thank you for sharing your time and energy with the ${conf-name}
community!
@@ -1490,18 +1830,20 @@ as soon as you can and I'll try to shuffle things around. Thank you!")
"Send checkin instructions to speakers who will be there.
GROUP is (email . (talk talk))"
(interactive (list (emacsconf-mail-complete-email-group
- (seq-remove
- (lambda (o)
- (or
- (string= (plist-get o :status) "CANCELLED")
- (null (plist-get o :email))
- (string-match "after" (or (plist-get o :q-and-a) ""))))
- (emacsconf-get-talk-info)))))
+ (emacsconf-filter-talks-by-logbook
+ "sent check-in information"
+ (seq-remove
+ (lambda (o)
+ (or
+ (string= (plist-get o :status) "CANCELLED")
+ (null (plist-get o :email))
+ (string-match "after" (or (plist-get o :qa-type) ""))))
+ (emacsconf-get-talk-info))))))
(emacsconf-mail-prepare
(list
:subject "${conf-name} ${year}: Check-in instructions"
- :reply-to "emacsconf-submit@gnu.org, ${email}, ${user-email}"
- :mail-followup-to "emacsconf-submit@gnu.org, ${email}, ${user-email}"
+ :reply-to "${user-email}"
+ :mail-followup-to "${user-email}"
:log-note "sent check-in information for people who will be there"
:body
"${email-notes}Hello, ${speakers-short}!
@@ -1509,23 +1851,19 @@ GROUP is (email . (talk talk))"
We're looking forward to having you join us at ${conf-name}!
Here's your talk page URL and checkin information:
-${checkin-info}
+${checkin-info}${bbb-tech-check-note}
-Please check in early so that we can deal with scheduling changes
+Please check in earlyish so that we can deal with scheduling changes
or technical issues, and so that we don't worry too much about
missing speakers (aaah!). You can find the check-in
process at ${base-url}${year}/speakers/ . ${wrap}
-${waiting}If something comes up, please let us know as soon as you can. Here's
-my emergency contact information: ${emergency}${wrap}
+${waiting}If something comes up, please let us know as soon as you can.${wrap}
Thank you for sharing your time and energy with the ${conf-name}
community!
-${signature}
-
-p.s. If you need to cancel, that's okay too, life happens. Let me know
-as soon as you can and I'll try to shuffle things around. Thank you!")
+${signature}")
(car group)
(list
:year emacsconf-year
@@ -1542,9 +1880,17 @@ as soon as you can and I'll try to shuffle things around. Thank you!")
:waiting (let ((waiting (seq-remove (lambda (o) (plist-get o :video-file)) (cdr group))))
(cond
((= (length waiting) 0) "")
- ((= (length waiting) 1) "If you happen to be able to get a pre-recorded video together in the next few days, I think we might be able to still manage that.${fill}\n\n")
- (t "If you happen to be able to get your pre-recorded videos together in the next few days, I think we might be able to still manage them.${fill}\n\n")))
+ ((= (length waiting) 1) "If you happen to be able to get a pre-recorded video together, I think we might be able to still manage that.${fill}\n\n")
+ (t "If you happen to be able to get your pre-recorded videos together, I think we might be able to still manage them.${fill}\n\n")))
:signature user-full-name
+ :bbb-tech-check-note
+ (if (seq-find (lambda (o)
+ (or (plist-get o :live)
+ (null (plist-get o :video-file))
+ (string= (plist-get o :qa-type) "live")))
+ (cdr group))
+ "\n\nIt might be a good idea to do a tech check to make sure I didn't mess up BigBlueButton. =) Feel free to connect to your BigBlueButton room before the conference using the URL and moderator code above so that you can try your audio, screensharing (optional), webcam (optional), etc. The server is up at the moment and should continue to be up until the conference, so you can do your tech check early if you want.${wrap}"
+ "")
:checkin-info
(mapconcat
(lambda (o)
@@ -1560,13 +1906,23 @@ as soon as you can and I'll try to shuffle things around. Thank you!")
:qa-info-speakers
(cond
((or (plist-get o :live) (null (plist-get o :video-file))) ;; intentionally a live talk
- (concat "Talk & Q&A BigBlueButton room: "
- (emacsconf-backstage-url (plist-get o :bbb-backstage))))
+ (unless (plist-get o :bbb-room) (error "No BBB room for %s" (plist-get o :slug)))
+ (unless (plist-get o :bbb-mod-code) (error "No BBB mod code for %s" (plist-get o :slug)))
+ (concat
+ "Use the moderator code "
+ (plist-get o :bbb-mod-code)
+ "\n for this talk & Q&A BigBlueButton room: "
+ (emacsconf-backstage-url (plist-get o :bbb-backstage))))
((string= (plist-get o :qa-type) "none")
"Q&A: After the event; we'll collect the questions and e-mail them to you")
((string= (plist-get o :qa-type) "live")
- (concat "Q&A BigBlueButton room: "
- (emacsconf-backstage-url (plist-get o :bbb-backstage))))
+ (unless (plist-get o :bbb-room) (error "No BBB room for %s" (plist-get o :slug)))
+ (unless (plist-get o :bbb-mod-code) (error "No BBB mod code for %s" (plist-get o :slug)))
+ (concat
+ "Use the moderator code "
+ (plist-get o :bbb-mod-code)
+ "\n for this Q&A BigBlueButton room: "
+ (emacsconf-backstage-url (plist-get o :bbb-backstage))))
((string= (plist-get o :qa-type) "irc")
(concat "Q&A: On IRC: #" (plist-get o :channel) " ( " (plist-get o :webchat-url) " )"))
((string= (plist-get o :qa-type) "pad")
@@ -1626,7 +1982,7 @@ Sacha")
(cdr group) " , ")
:email-notes (emacsconf-surround "ZZZ: " (plist-get (cadr group) :email-notes) "\n\n" ""))))
-(defun emacsconf-mail-template-speakers-thanks-after-conferences ()
+(defun emacsconf-mail-template-speakers-thanks-after-conference ()
(interactive)
(let* ((log-note "sent thanks to speaker after conference")
(groups
@@ -1639,8 +1995,8 @@ Sacha")
(emacsconf-mail-prepare
(list
:subject "Thanks for speaking at ${conf-name} ${year}!"
- :reply-to "emacsconf-submit@gnu.org, ${email}, ${user-email}"
- :mail-followup-to "emacsconf-submit@gnu.org, ${email}, ${user-email}"
+ :reply-to "${user-email}"
+ :mail-followup-to "${user-email}"
:log-note log-note
:body
"${email-notes}Hi, ${speakers-short}!
@@ -1649,18 +2005,22 @@ Thank you so much for being part of ${conf-name} ${year}! Hundreds of people
enjoyed it, and I'm sure even more will come across the videos in the
days to follow.
-Your videos are available on the talk page at ${talk-urls} , and
-we've added the questions and comments that we've collected from
-IRC/BBB/Etherpad. For your convenience, I've also included them below.
+We've added the questions and comments that we've collected from
+IRC/BBB/Etherpad to ${talk-urls} . For your convenience, I've also
+included them below. You can edit the wiki directly or e-mail me
+anything you'd like me to add.${wrap}
-Your videos are also available on YouTube and Toobnix at:
+Videos are also available on YouTube and Toobnix at:
${video-urls}
-
-If you want to reupload the video to your own channel, feel free
-to do so. If you let me know where you've uploaded it, I can
-switch our playlist to include your version of the video
-instead. That way, it might be easier for you to respond to
+I'm waiting for people to check the audio of the Q&A videos and
+renormalize them if needed before I upload those to YouTube and Toobnix,
+but the Q&A videos are already available on the talk pages at ${wiki}
+along with chapter indices and rough transcripts.
+
+If you want to reupload the video to your own channel, feel free to do
+so. If you like, I can switch our playlist to include your version of
+the video instead. That way, it might be easier for you to respond to
comments on videos.
If you would like to share more resources or add more answers to
@@ -1681,6 +2041,7 @@ ${feedback}
:speakers-short (plist-get (cadr group) :speakers-short)
:conf-name emacsconf-name
:year emacsconf-year
+ :wiki (concat emacsconf-base-url emacsconf-year "/talks/")
:talk-urls
(mapconcat
(lambda (talk)
@@ -1801,26 +2162,235 @@ ${transcript}
(concat "Automatic captions for " (plist-get talk :title))
"attachment")))
+(defvar emacsconf-sticker-mailer nil "E-mail address of person who sends out stickers.")
+
+(defun emacsconf-mail-template-mailing-address (group)
+ (interactive (list (emacsconf-mail-complete-email-group)))
+ (emacsconf-mail-prepare
+ (list
+ :subject "${conf-name} ${year}: Can we send you a sticker or pin of appreciation?"
+ :reply-to "${user-email}, ${sticker-mailer}, ${email}"
+ :mail-followup-to "${user-email}, ${sticker-mailer}, ${email}"
+ :log-note "Asked for mailing address"
+ :body
+ "${email-notes}Hi, ${speakers-short}!
+
+We have swag again this year, thanks to Corwin
+Brust! Would you like a sticker or a pin as a
+small token of our appreciation? This is what they
+look like:
+
+https://bru.st/i/ecswag.jpg
+
+(It's also part of our Evil Plan: maybe people
+will see the sticker or the pin and talk to you
+about Emacs! =) )
+
+If you want one, please e-mail your mailing
+address and your preference* (sticker or pin) to
+corwin@bru.st . We promise to use your address
+only for sending it.
+
+(* While supplies last; Corwin thinks there should
+be plenty, but just in case, feel free to send us
+your second choice too.)
+
+Thank you so much for contributing to ${conf-name} ${year}!
+
+${signature}
+")
+ (car group)
+ (list
+ :email-notes (emacsconf-surround "ZZZ: " (string-join (seq-uniq (seq-map (lambda (talk) (plist-get talk :email-notes)) (cdr group)))
+ ", ") "\n\n" "")
+ :speakers-short (plist-get (cadr group) :speakers-short)
+ :conf-name emacsconf-name
+ :year emacsconf-year
+ :email (car group)
+ :base-url emacsconf-base-url
+ :signature user-full-name
+ :user-email user-mail-address
+ :sticker-mailer emacsconf-sticker-mailer)))
+
+(defun emacsconf-mail-template-mailing-address-to-all ()
+ "Ask for mailing address."
+ (interactive)
+ (let* ((log-note "asked for mailing address")
+ (groups
+ (emacsconf-mail-groups
+ (emacsconf-filter-talks-by-logbook
+ log-note
+ (seq-filter (lambda (o) (plist-get o :email))
+ (emacsconf-publish-prepare-for-display (emacsconf-get-talk-info)))))))
+ (dolist (group groups)
+ (emacsconf-mail-template-mailing-address group))
+ (message "Drafted %d messages" (length groups))))
+
+(defun emacsconf-mail-template-ask-volunteer-for-mailing-address (volunteer)
+ (interactive (list (emacsconf-complete-volunteer)))
+ (emacsconf-mail-prepare
+ (list
+ :subject "${conf-name} ${year}: Thank you! Can we send you a sticker or pin of appreciation?"
+ :reply-to "${user-email}, ${sticker-mailer}, ${email}"
+ :mail-followup-to "${user-email}, ${sticker-mailer}, ${email}"
+ :body
+ "Hi, ${name-short}!
+
+${email-notes}
+
+We have swag this year, thanks to Corwin
+Brust! Would you like a sticker or a pin as a
+small token of our appreciation? This is what they
+look like:
+
+https://bru.st/i/ecswag.jpg
+
+(It's also part of our Evil Plan: maybe people
+will see the sticker or the pin and talk to you
+about Emacs! =) )
+
+If you want one, please e-mail your mailing
+address and your preference* (sticker or pin) to
+corwin@bru.st . We promise to use your address
+only for sending it.
+
+(* While supplies last; Corwin thinks there should
+be plenty, but just in case, feel free to send us
+your second choice too.)
+
+Thank you so much for contributing to ${conf-name} ${year}!
+
+${signature}
+")
+ (assoc-default "EMAIL" volunteer)
+ (list
+ :email-notes
+ (emacsconf-surround
+ "ZZZ: "
+ (replace-regexp-in-string
+ ":volunteer" ""
+ (assoc-default "ALLTAGS" volunteer))
+ "\n\n" "")
+ :name-short (assoc-default "NAME_SHORT" volunteer)
+ :conf-name emacsconf-name
+ :year emacsconf-year
+ :email (assoc-default "EMAIL" volunteer)
+ :base-url emacsconf-base-url
+ :signature user-full-name
+ :user-email user-mail-address
+ :sticker-mailer emacsconf-sticker-mailer)))
+
+(defun emacsconf-mail-template-mail-youtube-comments (group)
+ "Send more YouTube comments."
+ (interactive (list (emacsconf-mail-complete-email-group
+ (seq-filter
+ (lambda (o)
+ (and
+ (or
+ (emacsconf-talk-file o "--answers--original.vtt")
+ (emacsconf-talk-file o "--original.vtt"))
+ (not (string-match "Asked for permission regarding the rest of the Q&A"
+ (plist-get o :logbook)))))
+ (emacsconf-get-talk-info)))))
+ (emacsconf-mail-prepare
+ (list
+ :subject "${conf-name} ${year}: May we post the rest of the Q&A?"
+ :reply-to "emacsconf-submit@gnu.org, ${email}, ${user-email}"
+ :mail-followup-to "emacsconf-submit@gnu.org, ${email}, ${user-email}"
+ :log-note "Asked for permission regarding the rest of the Q&A"
+ :body
+ "${email-notes}Hi, ${speakers-short}!
+
+We're experimenting with a new harvesting workflow for live and
+Q&A videos this year to make things more predictable for speakers
+and participants. Sometimes people have so much fun chatting
+after the talk that they might forget that the recording for the
+session Q&A will be posted for other people to learn from.
+
+I've trimmed your online videos to roughly when the host left the
+BigBlueButton room. There was lots of great discussion
+afterwards, though, so I'd love to include the rest of it if
+that's okay with you. To make it easier for you to review that
+part or reuse what you shared in the Q&A session, I've included
+an automatically-generated transcript for the whole Q&A
+session. I've indicated the section that got trimmed out of the
+published recording with \"NOTE Start of section to review\" in
+the transcript. You can watch the session at ${bbb-recording-url} .
+
+- Option A: We could post the rest of the Q&A as is, which lets
+ people listen to the conversation and learn from it
+
+- Option B: We can keep the published Q&A video to just the part
+ that the host was in, and either you or I can go over the
+ transcript to pull out interesting notes for the summary or for
+ other posts
+
+What do you think?
+
+${signature}
+----
+${transcript}
+")
+ (car group)
+ (list
+ :email-notes (emacsconf-surround "ZZZ: " (string-join (seq-uniq (seq-map (lambda (talk) (plist-get talk :email-notes)) (cdr group)))
+ ", ") "\n\n" "")
+ :speakers-short (plist-get (cadr group) :speakers-short)
+ :conf-name emacsconf-name
+ :year emacsconf-year
+ :bbb-recording-url
+ (mapconcat
+ (lambda (talk)
+ (plist-get talk :bbb-rec))
+ (cdr group)
+ " , ")
+ :signature user-full-name
+ :email (car group)
+ :transcript
+ (mapconcat
+ (lambda (talk)
+ (concat
+ (plist-get talk :title) "\n\n"
+ (mapconcat
+ (lambda (sub)
+ (concat (emacsconf-surround "\nNOTE " (elt sub 4) "\n\n" "")
+ (elt sub 3)))
+ (subed-parse-file (or (emacsconf-talk-file talk "--answers--original.vtt")
+ (emacsconf-talk-file talk "--original.vtt"))) "\n")))
+ (cdr group)
+ "----")
+ :user-email user-mail-address))
+ (dolist (talk (cdr group))
+ (mml-attach-file (or (emacsconf-talk-file talk "--answers--original.vtt")
+ (emacsconf-talk-file talk "--original.vtt"))
+ "text/vtt"
+ (concat "Automatic captions for " (plist-get talk :title))
+ "attachment")))
+
;;; Other mail functions
-(defun emacsconf-mail-verify-delivery (groups subject)
+(defun emacsconf-mail-verify-delivery (subject &optional groups)
"Verify that the email addresses in GROUPS have all received an email with SUBJECT."
- (interactive (list (emacsconf-mail-groups (seq-filter (lambda (o) (not (string= (plist-get o :status) "CANCELLED")))
- (emacsconf-get-talk-info)))
- (read-string "Subject: ")))
+ (interactive (list (read-string "Subject: ")))
+ (setq groups (or groups
+ (emacsconf-mail-groups (seq-filter (lambda (o)
+ (and (not (string= (plist-get o :status) "CANCELLED"))
+ (plist-get o :email)))
+ (emacsconf-get-talk-info)))))
(let ((missing
(seq-keep (lambda (group)
- (and (string= "0"
- (string-trim
- (shell-command-to-string
- (format "notmuch count to:%s and to:%s and subject:%s"
- (shell-quote-argument (car group))
- (shell-quote-argument emacsconf-mail-bcc-email)
- subject))))
- (car group)))
+ (let ((cmd (format "notmuch count to:%s and to:%s and subject:\\\"%s\\\""
+ (shell-quote-argument (car group))
+ (shell-quote-argument emacsconf-mail-bcc-email)
+ subject)))
+ (and (string= "0"
+ (string-trim
+ (shell-command-to-string
+ cmd)))
+ (car group))))
groups)))
(if missing
- (prin1 missing)
+ (message "Missing: %s" (string-join missing ", "))
(message "All good."))))
(defun emacsconf-mail-get-all-email-addresses (talk)
@@ -1927,11 +2497,13 @@ This minimizes the risk of mail delivery issues and radio silence."
(read-string (format "Note for %s: "
(mapconcat (lambda (o) (plist-get o :slug))
talks", "))))))
- (save-window-excursion
- (mapc
- (lambda (talk)
- (emacsconf-add-to-talk-logbook talk note))
- (emacsconf-mail-talks email))))
+ (condition-case e
+ (save-window-excursion
+ (mapc
+ (lambda (talk)
+ (emacsconf-add-to-talk-logbook talk note))
+ (emacsconf-mail-talks email)))
+ (error (message "Error %s" e))))
(defun emacsconf-mail-notmuch-save-attachments-to-cache (talk)
"Save the attached files to the cache and backstage dir for TALK."
@@ -1956,5 +2528,44 @@ This minimizes the risk of mail delivery issues and radio silence."
part (expand-file-name new-filename emacsconf-backstage-dir)))))
(mm-dissect-buffer))))
+;;;###autoload
+(defun emacsconf-notmuch-submissions ()
+ "Search for recent submissions."
+ (interactive)
+ (notmuch-search (format "to:%s and not subject:\"requires approval\" and not subject:\"moderator request(s) waiting\" and not from:no-reply@netdata.cloud" emacsconf-submit-email)))
+
+;;;###autoload
+(defun emacsconf-notmuch-new-submissions ()
+ "Search for recent submissions."
+ (interactive)
+ (notmuch-search (format "to:%s and not subject:\"requires approval\" and not subject:\"moderator request(s) waiting\" and not from:no-reply@netdata.cloud and not tag:replied and not tag:sent" emacsconf-submit-email)))
+
+(defun emacsconf-notmuch-check-sent (query &optional groups)
+ (interactive "MSubject: ")
+ (setq groups
+ (or groups
+ (emacsconf-mail-groups
+ (seq-filter (lambda (o) (plist-get o :email))
+ (emacsconf-publish-prepare-for-display (emacsconf-get-talk-info))))))
+ (let* ((files
+ (with-temp-buffer
+ (notmuch-call-notmuch--helper
+ t
+ (list "search" "--output" "files" (format "\"%s\"" query)))
+ (split-string (string-trim (buffer-string)) "\n")))
+ (sent-to
+ (cl-loop for f in files
+ collect
+ (with-temp-buffer
+ (insert-file-contents f)
+ (goto-char (point-min))
+ (when (re-search-forward "To: \\(.+\\)" nil t)
+ (match-string 1)))))
+ (missing (seq-difference (mapcar 'car groups)
+ sent-to)))
+ (if missing
+ (message "Missing: %s" (string-join missing "; "))
+ (message "All sent."))))
+
(provide 'emacsconf-mail)
;;; emacsconf-mail.el ends here
diff --git a/emacsconf-pad.el b/emacsconf-pad.el
index 0a49bd6..fe52fd1 100644
--- a/emacsconf-pad.el
+++ b/emacsconf-pad.el
@@ -142,7 +142,9 @@ You can find it in $ETHERPAD_PATH/APIKEY.txt"
(format "https://etherpad.wikimedia.org/p/emacsconf-%s-%s"
emacsconf-year
(plist-get o :slug))
- (concat emacsconf-pad-base emacsconf-pad-directory (emacsconf-pad-id o))))
+ (if (and (listp o) (plist-get o :slug))
+ (concat emacsconf-pad-base emacsconf-pad-directory (emacsconf-pad-id o))
+ (concat emacsconf-pad-base o))))
(defvar emacsconf-pad-number-of-next-talks 3 "Integer limiting the number of next talks to link to from the pad.")
@@ -175,6 +177,8 @@ You can find it in $ETHERPAD_PATH/APIKEY.txt"
"\n")
"</ul></div>")
"")
+ :pronouns (emacsconf-surround " (" (plist-get o :pronouns) ")" "")
+ :pronunciation (emacsconf-surround " - Pronunciation: " (plist-get o :pronunciation) "" "")
:track-id
(plist-get (emacsconf-get-track (plist-get o :track)) :id)
:watch
@@ -186,7 +190,7 @@ You can find it in $ETHERPAD_PATH/APIKEY.txt"
:questions
(string-join (make-list 6 "<li>Q: <ul><li>A: </li></ul></li>"))
:conf-pad-url
- (concat "https://pad.emacsconf.org/" emacsconf-year)
+ (concat "https://pad.emacsconf.org/emacsconf")
:irc-nick-details
(if (plist-get o :irc)
(concat "Speaker nick: " (plist-get o :irc) " - ")
@@ -196,7 +200,7 @@ You can find it in $ETHERPAD_PATH/APIKEY.txt"
"<div>
<div>All talks: ${talks}</div>
<div><strong>${title}</strong></div>
-<div>${base-url}${url} - ${speakers} - Track: ${track}</div>
+<div>${base-url}${url} - ${speakers}${pronouns}${pronunciation} - Track: ${track}</div>
<div>Watch/participate: ${watch}</div>
${bbb-info}
<div>IRC: ${irc-nick-details} https://chat.emacsconf.org/#/connect?join=emacsconf,emacsconf-${track-id} or #emacsconf-${track-id} on libera.chat network</div>
@@ -260,9 +264,10 @@ ${next-talk-list}
(emacsconf-pad-create-pad pad-id)
(when (or emacsconf-pad-force-all
(not (emacsconf-pad-modified-p pad-id))
- (progn
- (browse-url (emacsconf-pad-url o))
- (y-or-n-p (format "%s might have been modified. Reset? " (plist-get o :slug)))))
+ (and (called-interactively-p)
+ (progn
+ (browse-url (emacsconf-pad-url o))
+ (y-or-n-p (format "%s might have been modified. Reset? " (plist-get o :slug))))))
(emacsconf-pad-set-html
pad-id
(emacsconf-pad-initial-content o))
@@ -339,6 +344,7 @@ ${next-talk-list}
(defun emacsconf-pad-format-shift-hyperlist (shift info)
(let* ((track (emacsconf-get-track (plist-get shift :track)))
+ (shift-rtmp (seq-find (lambda (entry) (string= (assoc-default "ID" entry) (plist-get shift :id))) emacsconf-rtmp-shifts))
(prefixed (list
:start (plist-get shift :start)
:end (plist-get shift :end)
@@ -353,10 +359,17 @@ ${next-talk-list}
:irc-volunteer (format "<em>%s</em>" (emacsconf-surround "IRC-" (plist-get shift :irc) "" "IRC"))
:track-id (plist-get track :id)
:conf-id emacsconf-id
+ :channel (concat emacsconf-id "-" (plist-get track :id))
:checkin (format "<em>%s</em>" (emacsconf-surround "CHECKIN-" (plist-get shift :checkin) "" "CHECKIN"))
:pad (format "<em>%s</em>" (emacsconf-surround "PAD-" (plist-get shift :pad) "" "PAD"))
:coord (format "<em>%s</em>" (emacsconf-surround "COORD-" (plist-get shift :coord) "" "COORD"))
- :checkin-pad (concat emacsconf-pad-base "checkin-" (downcase (format-time-string "%a" (date-to-time (plist-get shift :start)))))))
+ :youtube-rtmp (assoc-default "YouTube" shift-rtmp 'string=)
+ :youtube-studio-url (assoc-default "YouTube URL" shift-rtmp 'string=)
+ :youtube-view-url
+ (replace-regexp-in-string
+ "https://studio\\.youtube\\.com/video/\\([^/]+\\)/livestreaming" "https://youtube.com/live/\\1"
+ (assoc-default "YouTube URL" shift-rtmp 'string=))
+ :checkin-pad (concat emacsconf-pad-base "private-" emacsconf-private-pad-prefix "-" emacsconf-year "-checkin-" (downcase (format-time-string "%a" (date-to-time (plist-get shift :start)))))))
(shift-talks
(mapcar (lambda (o) (append prefixed o))
(seq-filter
@@ -384,29 +397,29 @@ ${next-talk-list}
<strong>Setup</strong>
<ul>
-<li>[ ] ${checkin}: Open ${checkin-pad}</li>
-<li>[ ] ${irc-volunteer}: Watch the #emacsconf-${track-id} channel and open ${base-url}${year}/talks for links to the pads</li>
-<li>[ ] ${pad}: Open ${base-url}${year}/talks for links to the pads</li>
-<li>[ ] ${coord}: ssh orga@live0.emacsconf.org and run screen-fallbacks; confirm that the streams are showing fallbacks</li>
-<li>[ ] ${stream}: Start recording with OBS
-<li>[ ] Copy the password file if you don't already have it: <strong>scp emacsconf-${track-id}@res.emacsconf.org:~/.vnc/passwd vnc-passwd-${track-id} -p ${ssh-port}</strong></li>
-<li>[ ] Forward your local ports: <strong>ssh emacsconf-${track-id}@res.emacsconf.org -N -L ${vnc-port}:127.0.0.1:${vnc-port} -p ${ssh-port} &</strong></li>
-<li>[ ] Connect via VNC: <strong>xvncviewer 127.0.0.1:${vnc-port} -shared -geometry 1280x720 -passwd vnc-passwd-${track-id} &</strong>
-<ul>
-<li>[? Can't connect to VNC]: ssh emacsconf-${track-id}@res.emacsconf.org -p ${ssh-port} /home/${conf-id}-${track-id}/bin/track-vnc</li>
-<li>[? Can't find OBS]: track-obs</li></ul></li>
-<li>[ ] Start background music via SSH or VNC: <em>music</em>
-<ul><li>[? No audio device]:
-<ul><li><em>pulseaudio -k; pulseaudio --start</em></li>
-<li>quit OBS</li>
-<li><em>track-obs</em></li></ul></li>
-<li>[ ] Start recording (not streaming). (Alt-2, switch to workspace 2; Alt-Shift-2, move something to workspace 2).</li>
-<li>[ ] Watch the stream with MPV on your local system: <strong>mpv https://live0.emacsconf.org/emacsconf/${track-id}.webm &</strong></li>
-<li>[ ] Check 480p by viewing it : <strong>mpv https://live0.emacsconf.org/emacsconf/${track-id}-480p.webm &</strong></li>
-<li>[ ] Confirm that the streaming user has connected to Mumble, is in the ${channel} channel, and can hear what we say on Mumble.</li>
-<li>[ ] Test with a sample video or Q&A session. You can run this command on your local system if you want to do things off-screen: <strong>ssh emacsconf-${track-id}@res.emacsconf.org -p 46668 \"~/bin/track-mpv emacsconf &\"</strong></li>
-<li>[ ] ${stream}: Restart the background music via SSH or VNC: <em>music</em> . The background music should automatically get killed when the talks start, but if it doesn't, you can stop it with: <em>screen -S background -X quit</em></li>
-</ul></li>"
+ <li>[ ] ${checkin}: Open ${checkin-pad}</li>
+ <li>[ ] ${irc-volunteer}: Watch the #emacsconf-${track-id} channel and open ${base-url}${year}/talks for links to the pads</li>
+ <li>[ ] ${pad}: Open ${base-url}${year}/talks for links to the pads</li>
+ <li>[ ] Copy the password file if you don't already have it: <strong>scp emacsconf-${track-id}@res.emacsconf.org:~/.vnc/passwd vnc-passwd-${track-id} -p ${ssh-port}</strong></li>
+ <li>[ ] Forward your local ports: <strong>ssh emacsconf-${track-id}@res.emacsconf.org -N -L ${vnc-port}:127.0.0.1:${vnc-port} -p ${ssh-port} &</strong></li>
+ <li>[ ] Connect via VNC: <strong>xvncviewer 127.0.0.1:${vnc-port} -shared -geometry 1280x720 -passwd vnc-passwd-${track-id} &</strong>
+ <ul>
+ <li>[? Can't connect to VNC]: ssh emacsconf-${track-id}@res.emacsconf.org -p ${ssh-port} /home/${conf-id}-${track-id}/bin/track-vnc</li>
+ <li>[? Can't find OBS]: track-obs</li></ul></li>
+ <li>[ ] Start background music via SSH or VNC: <em>music</em>
+ <ul><li>[? No audio device]:
+ <ul><li><em>pulseaudio -k; pulseaudio --start</em></li>
+ <li>quit OBS</li>
+ <li><em>track-obs</em></li></ul>
+ </li></ul></li>
+ <li>[ ] OBS - Settings - update the RTMP stream key: <strong>${youtube-rtmp}</strong></li>
+ <li>[ ] Start recording AND start streaming. (Alt-2, switch to workspace 2; Alt-Shift-2, move something to workspace 2).</li>
+ <li>[ ] Watch the stream with MPV on your local system: <strong>mpv https://live0.emacsconf.org/emacsconf/${track-id}.webm &</strong></li>
+ <li>[ ] Check 480p by viewing it : <strong>mpv https://live0.emacsconf.org/emacsconf/${track-id}-480p.webm &</strong></li>
+ <li>[ ] Check YouTube: ${youtube-studio-url} and ${youtube-view-url}</li>
+ <li>[ ] Confirm that the streaming user has connected to Mumble, is in the ${channel} channel, and can hear what we say on Mumble.</li>
+ <li>[ ] Test with a sample video or Q&A session. You can run this command on your local system if you want to do things off-screen: <strong>ssh emacsconf-${track-id}@res.emacsconf.org -p 46668 \"~/bin/track-mpv emacsconf &\"</strong></li>
+ <li>[ ] ${stream}: Restart the background music via SSH or VNC: <em>music</em> . The background music should automatically get killed when the talks start, but if it doesn't, you can stop it with: <em>screen -S background -X quit</em></li>"
(if emacsconf-restream-youtube
"<li>[ ] ${coord}: ssh -t orga@live0.emacsconf.org 'screen -S restream-${track-id}-youtube /home/orga/restream-${track-id}-youtube.sh' and then confirm at ${youtube-url}</li>
" "")
@@ -430,7 +443,7 @@ ${next-talk-list}
"</ul>"
"Teardown
<ul>
-<li>[ ] ${stream}: stop recording</li>
+<li>[ ] ${stream}: stop recording and stop streaming</li>
"
(if emacsconf-restream-youtube
"
@@ -442,7 +455,7 @@ ${next-talk-list}
<li>[ ] ${coord}: stop the restream-${track-id}-toobnix screen on live0: <strong>screen -S restream-${track-id}-toobnix -X quit</strong></li>
"
"")
-"
+ "
<li>[ ] ${coord}: update the status page on live.emacsconf.org by changing emacsconf-tracks and calling emacsconf-stream-update-status-page</li>
</ul>"))
)))
@@ -476,7 +489,7 @@ ${next-talk-list}
(plist-get talk :timezone)
"%-l:%M %p"))
"<ul>
-<li>Message for the speaker: Thanks for checking in! Your BigBlueButton web conference room is at ${backstage-url}. If you don't have the backstage username and password saved, let me know and I can send you a direct message with the info. Please join me there so that I can set you as a moderator and go through the preflight checklist with you.
+<li>Message for the speaker: Thanks for checking in! Your BigBlueButton web conference room is at ${backstage-url}. If you don't have the backstage username and password saved, let me know and I can send you a direct message with the info. Please join me there so that I can set you as a moderator and go through the preflight checklist with you.</li>
<li>Direct message for the speaker if needed: Your BigBlueButton web conference room is at ${backstage-url-with-password}, or username \"${backstage-user}\" and password \"${backstage-password}\".</li>
<li>Pronunciation: ${pronunciation}</li>
<li>Checklist<ul>
@@ -485,9 +498,10 @@ ${next-talk-list}
<li>[ ] Speaker can hear others</li>
<li>[ ] No audio feedback issues (may need headphones or earphones)</li>
<li>[ ] Screen sharing: (optional)
-<ul><li>[ ] Window or screen can be shared
-<li>[ ] Text is readable</li></ul>
-<li>[ ] Webcam sharing (optional)</li></ul></li>
+<ul><li>[ ] Window or screen can be shared</li>
+<li>[ ] Text is readable</li></ul></li>
+<li>[ ] Webcam sharing (optional)</li>
+<li>[ ] What kind of facilitation would the speaker like? (Host reads questions, chats a lot, etc.)</li></ul></li>
<li>OK to do other things until going live at <strong>${live}</strong></li>
<li>People will add questions to the pad or IRC channel; host can read them to you, or you can read them</li>
<li>You can answer questions in any order, and you can skip questions if you want. Feel free to take your time to think about answers or to save some for following up later</li>
@@ -514,7 +528,8 @@ ${bbb-checklist}</li>")
(emacsconf-publish-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 ((base-pad (concat "private-" emacsconf-private-pad-prefix "-" emacsconf-year))
+ (pad-id (concat "private-" emacsconf-private-pad-prefix "-" emacsconf-year "-checkin-" (downcase (format-time-string "%a" (plist-get (cadr day) :checkin-time))))))
(emacsconf-pad-create-pad pad-id)
(emacsconf-pad-set-html
pad-id
@@ -522,17 +537,20 @@ ${bbb-checklist}</li>")
"<em>${checkin}:</em> " ""
(concat
(car day)
+ "\n"
+ emacsconf-pad-base base-pad "\n"
"<p>If anyone's still missing by the specified time, please let us know in #emacsconf-org so we can call them.</p>"
"<ul>"
- (mapconcat
- (lambda (talk)
- (emacsconf-pad-format-checkin-hyperlist talk))
- (seq-sort
- (lambda (a b)
- (time-less-p (plist-get a :checkin-time)
- (plist-get b :checkin-time)))
- (seq-filter (lambda (talk) (plist-get talk :checkin-time)) day))
- "\n")
+ (let ((body (mapconcat
+ (lambda (talk)
+ (emacsconf-pad-format-checkin-hyperlist talk))
+ (seq-sort
+ (lambda (a b)
+ (time-less-p (plist-get a :checkin-time)
+ (plist-get b :checkin-time)))
+ (seq-filter (lambda (talk) (plist-get talk :checkin-time)) day))
+ "\n")))
+ body)
"</ul>")))))
(seq-group-by (lambda (talk)
(format-time-string "%A, %b %-e, %Y" (plist-get talk :checkin-time)))
@@ -551,7 +569,9 @@ ${bbb-checklist}</li>")
:base-url emacsconf-base-url
:year emacsconf-year
:checkin-list (mapconcat
- (lambda (day) (concat "<li>" emacsconf-pad-base "checkin-"
+ (lambda (day) (concat "<li>" emacsconf-pad-base
+ id
+ "-checkin-"
(downcase (format-time-string "%a" (plist-get (cadr day) :checkin-time)))
"</li>"))
(seq-group-by (lambda (talk)
@@ -561,18 +581,19 @@ ${bbb-checklist}</li>")
"")
:shift-list (mapconcat
(lambda (shift)
- (format "<li>%sprivate-%s-%s-%s</li>"
+ (format "<li>%s%s-%s</li>"
emacsconf-pad-base
- emacsconf-private-pad-prefix
- emacsconf-year
+ id
(plist-get shift :id)))
emacsconf-shifts
"")
:host-list
(mapconcat
(lambda (shift)
- (format "<li>%shost-%s</li>"
+ (format "<li>%sprivate-%s-%s-host-%s</li>"
emacsconf-pad-base
+ emacsconf-private-pad-prefix
+ emacsconf-year
(plist-get shift :id)))
emacsconf-shifts
"")
@@ -589,7 +610,14 @@ ${bbb-checklist}</li>")
<div>Combined shift info:
<ul>${shift-list}</ul></div>
-"))))
+"))
+ (emacsconf-pad-url id)))
+
+(defun emacsconf-pad-shift-hyperlist-id (shift)
+ (format "private-%s-%s-%s"
+ emacsconf-private-pad-prefix
+ emacsconf-year
+ (plist-get (emacsconf-resolve-shift shift) :id)))
(defun emacsconf-pad-prepopulate-shift-hyperlist (shift &optional info)
(interactive (list (completing-read "Shift: "
@@ -598,15 +626,17 @@ ${bbb-checklist}</li>")
(setq shift (seq-find (lambda (o) (string= (plist-get o :id) shift)) emacsconf-shifts)))
(unless info (setq info (emacsconf-publish-prepare-for-display (emacsconf-get-talk-info))))
(let ((info (emacsconf-publish-prepare-for-display (emacsconf-get-talk-info)))
- (pad-id (format "private-%s-%s-%s"
- emacsconf-private-pad-prefix
- emacsconf-year
- (plist-get shift :id))))
+ (pad-id (emacsconf-pad-shift-hyperlist-id shift)))
(emacsconf-pad-create-pad pad-id)
(emacsconf-pad-set-html
pad-id
(emacsconf-pad-format-shift-hyperlist shift info))))
+(defun emacsconf-pad-open-shift-hyperlist (shift)
+ (interactive (list (completing-read "Shift: "
+ (mapcar (lambda (o) (plist-get o :id)) emacsconf-shifts))))
+ (browse-url (emacsconf-pad-url (emacsconf-pad-shift-hyperlist-id shift))))
+
(defun emacsconf-pad-prepopulate-host-hyperlists ()
(interactive)
(mapc #'emacsconf-pad-prepopulate-shift-hyperlist-host emacsconf-shifts))
@@ -618,7 +648,9 @@ ${bbb-checklist}</li>")
(setq shift (seq-find (lambda (o) (string= (plist-get o :id) shift)) emacsconf-shifts)))
(unless info (setq info (emacsconf-publish-prepare-for-display (emacsconf-get-talk-info))))
(let ((info (emacsconf-publish-prepare-for-display (emacsconf-get-talk-info))))
- (let* ((pad-id (format "host-%s"
+ (let* ((pad-id (format "private-%s-%s-host-%s"
+ emacsconf-private-pad-prefix
+ emacsconf-year
(plist-get shift :id)))
(shift-talks
(seq-filter
@@ -663,44 +695,44 @@ ${bbb-checklist}</li>")
(format-time-string "%-l:%M %p" (plist-get talk :start-time) emacsconf-timezone))
(plist-get talk :hyperlist-note) "</li>" "")
:next-talk-in-5 (if next-talk (format-time-string "%-l:%M %p" (time-subtract (plist-get next-talk :start-time) (seconds-to-time 300)) emacsconf-timezone) "")
+ :next-talk-in-2 (if next-talk (format-time-string "%-l:%M %p" (time-subtract (plist-get next-talk :start-time) (seconds-to-time 120)) emacsconf-timezone) "")
:next-talk-in-1 (if next-talk (format-time-string "%-l:%M %p" (time-subtract (plist-get next-talk :start-time) (seconds-to-time 60)) emacsconf-timezone) ""))
talk)
(concat
"${hyperlist-note-info}"
- (cond
- (;; live talk, join BBB
- (null (plist-get talk :video-file))
- "<li><strong>${start-hhmm} ${slug} live talk</strong>: it should play a prerecorded intro, but if it doesn't, join ${bbb-backstage} and introduce talk, then turn it over to speaker for <strong>live talk</strong>: ${expanded-intro} (pronunciation: ${pronunciation})</li>")
- (t ;; prerecorded talk
- "<li>Backup: ${start-hhmm} ${slug}: it should play a prerecorded intro and talk, but if it doesn't, join ${mumble} in Mumble and introduce talk: ${expanded-intro} (pronunciation: ${pronunciation}); then <em>play ${slug}</em></li>"))
+ (if (emacsconf-talk-recorded-p talk)
+ "<li>Backup: ${start-hhmm} ${slug}: it should play a prerecorded intro and talk, but if it doesn't, join ${mumble} in Mumble and introduce talk: ${expanded-intro} (pronunciation: ${pronunciation}); then <em>play ${slug}</em></li>"
+ ;; live talk, join BBB
+ "<li><strong>${start-hhmm} ${slug} live talk</strong>: it should play a prerecorded intro, but if it doesn't, use mod code <strong>${bbb-mod-code}</strong> to join ${bbb-backstage} and introduce talk, then turn it over to speaker for <strong>live talk</strong>: ${expanded-intro} (pronunciation: ${pronunciation})</li>")
;; Q&A
- (if (and (null (plist-get talk :video-file)) (not (string= (or (plist-get talk :q-and-a) "none") "none")))
+ (if (and (not (emacsconf-talk-recorded-p talk))
+ (not (string= (or (plist-get talk :qa-type) "none") "none")))
"<li>Continue in the BBB room for live Q&A because the talk was live</li>"
- (pcase (plist-get talk :q-and-a)
+ (pcase (plist-get talk :qa-type)
((or 'nil "" "none" (rx "after"))
(if (plist-get talk :video-file)
"<li>[ ] ${qa-hhmm} ${slug} Q&A after: Join ${mumble} in Mumble and say that the speaker will follow up with answers on the talk page afterwards. Read questions. ${pad-url}</li>"
""))
- ((rx "IRC")
+ ((rx "irc")
"<li>[ ] ${qa-hhmm} ${slug} Q&A IRC: Join ${mumble} in Mumble. Invite people to put their questions in the ${channel} IRC channel and read questions and answers from there. ${webchat-url} ${pad-url}</li>")
((rx "pad")
"<li>[ ] <strong>${qa-hhmm}</strong> ${slug} Q&A pad: Join ${mumble} in Mumble. Invite people to put their questions in the Etherpad and read questions and answers from there. ${pad-url}</li>")
- ((rx "Mumble")
+ ((rx "mumble")
"<li>[ ] <strong>${qa-hhmm}</strong> ${slug} Q&A mumble: Join ${mumble} in Mumble. Bring the speaker into the right channel if needed. Invite people to put their questions in the Etherpad and read questions and answers from there. ${pad-url} Paste questions into Mumble chat or read them out loud.</li>")
((rx "live")
(concat
- "<li>[ ] <strong>${qa-hhmm} ${slug} Q&A live</strong> (on stream until ${end-of-qa}): Join ${bbb-backstage}. START RECORDING. Invite people to put their questions in the Etherpad, and read questions from there. ${pad-url}</li>
+ "<li>[ ] <strong>${qa-hhmm} ${slug} Q&A live</strong> (on stream until ${end-of-qa}): Use mod code <strong>${bbb-mod-code}</strong> to join ${bbb-backstage} . START RECORDING. Invite people to put their questions in the Etherpad, and read questions from there. ${pad-url}</li>
${open-qa}
"
- (if next-talk
- "
+ (if next-talk
+ "
<li><strong>${next-talk-in-5}</strong> [? Open Q&A is still going on and it's about five minutes before the next talk]
<ul><li>[ ] Let the speaker know about the time and that the Q&A can continue off-stream if people want to join</li></ul></li>
-<li><strong>${next-talk-in-1}</strong> [? Open Q&A is still going on and it's about a minute before the next talk]
+<li><strong>${next-talk-in-2}</strong> [? Open Q&A is still going on and it's about 2 minutes before the next talk]
<ul><li>[ ] 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</li></ul></li>
"
- ""))
-)))))))
+ ""))
+ )))))))
(emacsconf-include-next-talks shift-talks 1)
"\n")
"</ul>")))))
@@ -716,7 +748,8 @@ ${bbb-checklist}</li>")
(interactive)
(emacsconf-pad-prepopulate-shift-hyperlists)
(emacsconf-pad-prepopulate-checkins)
- (emacsconf-pad-prepopulate-host-hyperlists))
+ (emacsconf-pad-prepopulate-host-hyperlists)
+ (emacsconf-pad-prepopulate-index))
(defun emacsconf-pad-expand-intro (talk)
"Make an intro for TALK."
@@ -729,30 +762,35 @@ ${bbb-checklist}</li>")
(let ((pronoun (pcase (plist-get talk :pronouns)
((rx "she") "She")
((rx "\"ou\"" "Ou"))
- ((or 'nil "nil" (rx string-start "he") (rx "him")) "He")
+ ((or 'nil "nil") nil)
+ ((or (rx string-start "he") (rx "him")) "He")
((rx "they") "They")
- (_ (or (plist-get talk :pronouns) "")))))
- (format "Next, we have \"%s\", by %s%s.%s"
+ (_ (plist-get talk :pronouns)))))
+ (format "Next, we have \"%s\",\nby %s%s.%s"
(plist-get talk :title)
(replace-regexp-in-string ", \\([^,]+\\)$"
", and \\1"
(plist-get talk :speakers))
(emacsconf-surround " (" (plist-get talk :pronunciation) ")" "")
- (pcase (plist-get talk :q-and-a)
+ (pcase (plist-get talk :qa-type)
((or 'nil "") "")
- ((rx "after") " You can ask questions via Etherpad and IRC. We'll send them to the speaker, and we'll post the answers on the talk page afterwards.")
- ((rx "live")
- (format " %s will answer questions via BigBlueButton. You can join using the URL from the talk page or ask questions through Etherpad or IRC."
- pronoun
- ))
- ((rx "pad")
- (format " %s will answer questions via Etherpad."
- pronoun
- ))
- ((rx "IRC")
- (format " %s will answer questions via IRC in the #%s channel."
- pronoun
- (plist-get talk :channel)))))))))
+ ("none" "\nYou can ask questions via Etherpad and IRC.\nWe'll send them to the speaker,\nand we'll post the answers on the talk page afterwards.")
+ ("live"
+ (if pronoun
+ (format "\n%s will answer questions via web conference.\nYou can join using the URL from the talk page\nor ask questions through Etherpad or IRC."
+ pronoun)
+ "\nYou can ask questions via web conference by joining from the talk page\nor ask questions through Etherpad or IRC."))
+ ("pad"
+ (if pronoun
+ (format "\n%s will answer questions via Etherpad." pronoun)
+ "\nYou can ask questions via Etherpad."))
+ ("irc"
+ (if pronoun
+ (format "\n%s will answer questions via IRC in the #%s channel."
+ pronoun
+ (plist-get talk :channel))
+ (format "\nYou can ask questions via IRC in the #%s channel."
+ (plist-get talk :channel))))))))))
;; Related: emacsconf-talk-hyperlist
(defun emacsconf-pad-talk-hyperlist (talk &optional do-insert)
@@ -770,7 +808,7 @@ ${bbb-checklist}</li>")
:media-base emacsconf-media-base-url
:mumble (concat emacsconf-id "-" track-id)
:next-talk-in-5 (if next-talk (format-time-string "%-l:%M %p" (time-subtract (plist-get next-talk :start-time) (seconds-to-time 300)) emacsconf-timezone) "")
- :next-talk-in-1 (if next-talk (format-time-string "%-l:%M %p" (time-subtract (plist-get next-talk :start-time) (seconds-to-time 60)) emacsconf-timezone) "")
+ :next-talk-in-2 (if next-talk (format-time-string "%-l:%M %p" (time-subtract (plist-get next-talk :start-time) (seconds-to-time 120)) emacsconf-timezone) "")
:qa-start (format-time-string "%-l:%M %p" (plist-get talk :qa-time) emacsconf-timezone)
:qa-end (if next-talk (format-time-string "%-l:%M %p" (plist-get next-talk :start-time))
"end of shift")
@@ -791,7 +829,7 @@ ${bbb-checklist}</li>")
(setq result
(emacsconf-replace-plist-in-string
modified-talk
- (format "<li><strong>%s %s (intro: %s, talk: %s, Q&A: %s) %s <a href=\"%s\">%s</a></strong><ul>%s</ul>\n</li>"
+ (format "<li><strong>%s %s (intro: %s, talk: %s, Q&A: %s) %s <a href=\"%s\">%s</a></strong><br /><ul><li>%s%s%s</li>%s</ul>\n</li>"
(format-time-string "%-l:%M %p" (plist-get talk :start-time) emacsconf-timezone)
(plist-get talk :slug)
(if (plist-get talk :recorded-intro) "recorded" "live")
@@ -800,42 +838,41 @@ ${bbb-checklist}</li>")
(plist-get talk :title)
(plist-get talk :absolute-url)
(plist-get talk :absolute-url)
-
+ (emacsconf-surround "" (plist-get talk :speakers) "" "No speakers")
+ (emacsconf-surround " Pron: " (plist-get talk :pronunciation) "" "")
+ (emacsconf-surround " (" (plist-get talk :pronouns) ")" "")
(concat
(emacsconf-surround "<li><strong>" (plist-get talk :hyperlist-note) "</strong></li>" "")
"<li>Recorded intro: <a href=\"${media-base}${year}/backstage/${file-prefix}--intro.webm\">${media-base}${year}/backstage/${file-prefix}--intro.webm</a>"
- (if (plist-get talk :video-file)
- "<li>[ ] [? stream didn't auto-play] ${stream}: <em>handle-session ${slug}</em>; if that doesn't work, <em>play ${slug}</em>; if that still doesn't work, <em>track-mpv ~/current/cache/${conf-id}-${year}-${slug}*--intro.webm</em> and <em>track-mpv ~/current/cache/${conf-id}-${year}-${slug}*--main.webm</em></li>"
+ (if (emacsconf-talk-recorded-p talk)
+ "<li>[? stream didn't auto-play] ${stream}: <em>handle-session ${slug}</em></li>"
(concat
"<li>Live talk:<ul>"
- "<li>[ ] [? stream didn't auto-join] ${stream}: <a href=\"${bbb-backstage}\">${bbb-backstage}</a></li>"
- "<li>[ ] ${host}: Join <a href=\"${bbb-backstage}\">${bbb-backstage}</a> and turn over to speaker.</li></ul></li>"))
- (pcase (or (plist-get talk :q-and-a) "")
+ "<li>[? stream didn't auto-join] ${stream}: mod code ${bbb-mod-code} <a href=\"${bbb-backstage}\">${bbb-backstage}</a></li>"
+ "<li>[ ] ${host}: mod code ${bbb-mod-code} , join <a href=\"${bbb-backstage}\">${bbb-backstage}</a> and turn over to speaker.</li></ul></li>"))
+ (pcase (or (plist-get talk :qa-type) "")
((rx "live")
(concat
"<li>Live Q&A start ${qa-start}, on stream until ${qa-end}<ul>
-<li>[ ] ${host}: Join the Q&A room at <a href=\"${bbb-backstage}\">${bbb-backstage}</a> and open the pad at <a href=\"${pad-url}\">${pad-url}</a>; optionally open IRC for ${channel} (<a href=\"${webchat-url}\">${webchat-url}</a>)</li>
-<li>[ ] [? speaker missing?] ${host}: Let #emacsconf-org know so that we can text or call the speaker</li>
-<li>[ ] [? stream didn't auto-join?] ${stream}: <em>bbb ${slug}</em>
+<li>[ ] ${host}: Copy the modcode <strong>${bbb-mod-code}</strong> , join the Q&A room at <a href=\"${bbb-backstage}\">${bbb-backstage}</a>, and open the pad at <a href=\"${pad-url}\">${pad-url}</a>; optionally open IRC for ${channel} (<a href=\"${webchat-url}\">${webchat-url}</a>)</li>
+<li> [? speaker missing?] ${host}: Let #emacsconf-org know so that we can text or call the speaker</li>
+<li> [? stream didn't auto-join?] ${stream}: <em>bbb ${slug}</em>
<ul>
<li>Backup URL for BBB: <a href=\"${bbb-backstage}\">${bbb-backstage}</a></li>
<li>Backup URL for pad: <a href=\"${pad-url}\">${pad-url}</a></li>
</ul>
</li>
<li>[ ] ${stream}: Give the host the go-ahead via Mumble or #emacsconf-org</li>
-<li>[ ] ${host}: Start recording and read questions</li>
-<li>[ ] ${stream}: Adjust the audio levels as needed: ${ssh-audio}</li>
+<li>[ ] ${host}: Announce that people can join using the URL on the talk page or ask questions on the pad or IRC channel. START RECORDING.</li>
"
(if emacsconf-qa-start-open
""
- "<li>[ ] ${host}: Decide when to open the Q&A and let ${stream} know</li>
-<li>[ ] ${stream}: Update the task status (no visible changes): ${ssh-openq}</li>")
+ "<li>[ ] ${host}: Decide when to open the Q&A and let ${stream} know</li>")
"
-<li>[ ] ${stream}: Confirm BBB redirect at <a href=\"${bbb-redirect}\">${bbb-redirect}</a> goes to BBB room, let host know</li>
-<li>[ ] ${host}: Announce that people can join using the URL on the talk page or ask questions on the pad or IRC channel</li>
+<li>[ ] ${stream}: Confirm BBB redirect at <a href=\"${bbb-redirect}\">${bbb-redirect}</a> goes to BBB room, let host know; backup: <em>ssh orga@media.emacsconf.org \"~/bin/bbb-open ${slug}\"</em></li>
<li>${next-talk-in-5} [? Open Q&A is still going on and it's about five minutes before the next talk]
<ul><li>[ ] ${host}: Let the speaker know about the time and that the Q&A can continue off-stream if people want to join</li></ul></li>
-<li>${next-talk-in-1} [? Open Q&A is still going on and it's about a minute before the next talk]
+<li>${next-talk-in-2} [? Open Q&A is still going on and it's about 2 minutes before the next talk]
<ul><li>[ ] ${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</li></ul></li>
<li>[? Q&A is done early]
<ul>
@@ -845,30 +882,24 @@ ${bbb-checklist}</li>")
</ul></li></ul></li>"))
((rx "irc")
"
-<li>[ ] ${stream}: Update the task status, which should open the pad and IRC; arrange windows: ${ssh-closedq}
-<ul><li>Backup link to pad: <a href=\"${pad-url}\">${pad-url}</a></li>
-<li>Backup link to #${channel}: <a href=\"${webchat-url}\">${webchat-url}</a></li></ul></li>
-<li>[ ] ${stream}: Update the task status (no visible changes): ${ssh-openq}</li>
-<li>[ ] ${host}: Announce that people can ask questions in the ${channel} IRC channel.</li>
+<li>Backup link to pad: <a href=\"${pad-url}\">${pad-url}</a></li>
+<li>Backup link to #${channel}: <a href=\"${webchat-url}\">${webchat-url}</a></li>
")
((rx "Mumble")
"
<li>[ ] ${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)</li>
<li>[ ] ${stream}: Mark the Q&A as closed: ${ssh-closedq} . This should display the QA slide (backup: ${ssh-track} and run <em>firefox ${qa-slide-url} &</em>)</li>
-<li>[ ] ${stream}: Update the task status (no visible changes): ${ssh-openq}</li>
-
<li>[ ] ${host}: Announce that people can ask questions in the pad or on the ${channel} IRC channel.</li>
")
((rx "after")
"
-<li>[ ] ${stream}: Update the task status: ${ssh-closedq} # this should open the pad and IRC; arrange the windows
<ul><li>Backup link to pad: ${pad-url}</li>
<li>Backup link to #${channel}: ${webchat-url}</li></ul></li>
<li>[ ] ${host}: Announce that people can ask questions in the pad or on the ${channel} IRC channel, and that the speaker will follow up later.</li>
-<li>[ ] ${stream}: Update the task status: ${ssh-openq} # this should not make any visible changes, just update the task status</li>"
+"
)
((rx "pad")
- "<li>[ ] [? pad didn't auto-open] ${stream}: ${pad-url}</li>")
+ "<li>[? pad didn't auto-open] ${stream}: ${pad-url}</li>")
(_
"<li>[ ] ${stream}: Open the IRC channel (${channel}) and the pad, and arrange the windows: ${ssh-closedq}</li>
"))))))
diff --git a/emacsconf-publish.el b/emacsconf-publish.el
index c4a3872..a8884ea 100644
--- a/emacsconf-publish.el
+++ b/emacsconf-publish.el
@@ -30,7 +30,7 @@
:type 'string
:group 'emacsconf)
-(defcustom emacsconf-main-extensions '("--main.webm" "--main.opus" "--main.org" ".org" ".odp" ".pdf" ".pptx" ".el" "--compressed56.webm" "--main.vtt" "--main_fr.vtt" "--main_ja.vtt" "--main_es.vtt" "--main--chapters.vtt" "--script.fountain" "--main.pdf" "--slides.pdf")
+(defcustom emacsconf-main-extensions '("--main.webm" "--main.opus" "--main.org" ".org" ".odp" ".pdf" ".pptx" ".el" "--compressed56.webm" "--main.vtt" "--main_fr.vtt" "--main_ja.vtt" "--main_es.vtt" "--main--chapters.vtt" "--script.fountain" "--main.pdf" "--slides.pdf" "--answers.vtt" "--answers.webm")
"Extensions to list on public pages."
:type '(repeat string)
:group 'emacsconf)
@@ -46,7 +46,9 @@
(interactive (list (emacsconf-complete-talk-info)))
(let ((info (emacsconf-get-talk-info)))
(emacsconf-publish-before-page talk info)
- (emacsconf-publish-after-page talk info)))
+ (emacsconf-publish-after-page talk info)
+ (unless (emacsconf-publish-talk-p talk)
+ (emacsconf-publish-cancelled-nav-page talk))))
(defun emacsconf-publish-update-talk (talk)
"Publish the schedule page and the page for this talk."
@@ -63,8 +65,10 @@
(defun emacsconf-publish-add-talk ()
"Add the current talk to the wiki."
(interactive)
+ (emacsconf-current-org-notebook-refresh-schedule)
(emacsconf-publish-talk-page (emacsconf-get-talk-info-for-subtree))
(emacsconf-publish-info-pages)
+ (emacsconf-publish-schedule)
(magit-status-setup-buffer emacsconf-directory))
(defun emacsconf-publish-update-conf-html ()
@@ -142,78 +146,150 @@
(video (and file-prefix
(emacsconf-publish-index-card-video
(or (plist-get talk :video-id)
- (concat (plist-get talk :slug) "-mainVideo"))
+ (concat "mainVideo-" (plist-get talk :slug)))
video-file talk))))
;; Add extra information to the talk
(setq talk
(append
talk
(list
+ :video-type (or (plist-get talk :video-type) "mainVideo")
:time-info (emacsconf-surround "Duration: " (plist-get talk :video-duration) " minutes" "")
:video-html (or (plist-get video :video) "")
:audio-html (or (plist-get video :audio) "")
:chapter-list (or (plist-get video :chapter-list) "")
:resources (or (plist-get video :resources) "")
- :extra (or (plist-get talk :extra) "")
+ :extra
+ (concat
+ (if (plist-get talk :backstage)
+ ;; include schedule
+ (concat
+ "Starts: "
+ (format-time-string "%-I:%M %#p"
+ (plist-get talk :start-time))
+ " - Q&A: " (plist-get talk :q-and-a) "\n")
+ "")
+ (or (plist-get talk :extra) ""))
:speaker-info (or (plist-get talk :speakers-with-pronouns) ""))))
(emacsconf-replace-plist-in-string
talk
- "<div class=\"vid\">${video-html}${audio-html}<div>${extra}</div>${time-info}${resources}${chapter-list}</div>")))
+ "<div class=\"vid ${video-type}\">${video-html}${audio-html}<div>${extra}</div>${time-info}${resources}${chapter-list}</div>")))
;; (emacsconf-publish-format-track-as-org (car emacsconf-tracks) "US/Eastern")
;; (emacsconf-get-talk-info)
(defun emacsconf-publish-format-track-as-org (track tz &optional info)
- (setq info (or info (emacsconf-publish-prepare-for-display (emacsconf-get-talk-info))))
+ (let ((emacsconf-talk-info-functions (append emacsconf-talk-info-functions (list 'emacsconf-get-abstract-from-wiki))))
+ (setq info (or info (emacsconf-publish-prepare-for-display (emacsconf-get-talk-info)))))
(concat
"** " (plist-get track :name) " :" (plist-get track :id) ":\n:PROPERTIES:\n:CATEGORY: " (plist-get track :id) "\n:END:\n"
(mapconcat
(lambda (talk)
- (concat
- "*** " (plist-get talk :title) "\n"
- "<"
- (format-time-string
- (cdr org-time-stamp-formats)
- (plist-get talk :start-time)
- tz)
- ">--<"
- (format-time-string
- (cdr org-time-stamp-formats)
- (plist-get talk :end-time)
- tz)
- ">\n"
- (emacsconf-surround "- " (plist-get talk :speakers-with-pronouns) "\n" "")
- (emacsconf-surround "- " (plist-get talk :absolute-url) "\n" "")
- "- Watch live: "
- (mapconcat (lambda (player)
- (org-link-make-string
- (concat "shell:" player " " (plist-get track :stream) " &")
- player))
- '("mpv" "vlc" "ffplay")
- " or ")
- " or "
- (org-link-make-string
- (plist-get track :watch)
- "web-based player")
- "\n"
- (emacsconf-surround "- Etherpad: " (plist-get talk :pad-url) "\n" "")
- (emacsconf-surround "- Chat: "
- (org-link-make-string
- (plist-get talk :webchat-url)
- (concat "#" (plist-get talk :channel)))
- "\n" "")
- (emacsconf-surround "- Q&A: "
- (if (plist-get talk :qa-url)
- (org-make-link-string
- (plist-get talk :qa-url)
- (plist-get talk :qa-info))
- (plist-get talk :qa-info))
- "\n" "")
- (emacsconf-surround "\n" (plist-get talk :intro-note) "\n" "")))
+ (with-temp-buffer
+ (org-mode)
+
+ (concat
+ "*** " (plist-get talk :title) "\n"
+ "<"
+ (format-time-string
+ (cdr org-time-stamp-formats)
+ (plist-get talk :start-time)
+ tz)
+ ">--<"
+ (format-time-string
+ (cdr org-time-stamp-formats)
+ (plist-get talk :end-time)
+ tz)
+ ">\n"
+ (emacsconf-surround "- " (plist-get talk :speakers-with-pronouns) "\n" "")
+ (emacsconf-surround "- " (plist-get talk :absolute-url) "\n" "")
+ "- Watch live: "
+ (mapconcat (lambda (player)
+ (org-link-make-string
+ (concat "shell:" player " " (plist-get track :stream) " &")
+ player))
+ '("mpv" "vlc" "ffplay")
+ " or ")
+ " or "
+ (org-link-make-string
+ (plist-get track :watch)
+ "web-based player")
+ "\n"
+ "- You can also watch it in VLC by choosing menu - Media - Open Network Stream and putting in " (plist-get track :stream) "\n"
+ (emacsconf-surround "- Q&A: "
+ (if (plist-get talk :qa-url)
+ (org-make-link-string
+ (plist-get talk :qa-url)
+ (plist-get talk :qa-info))
+ (plist-get talk :qa-info))
+ "\n" "")
+ (emacsconf-surround "- Etherpad: " (plist-get talk :pad-url) "\n" "")
+ (emacsconf-surround " - Text version (no JS): " (plist-get talk :pad-url) "/export/txt\n" "")
+ (emacsconf-surround " - HTML version (no JS): " (plist-get talk :pad-url) "/export/html\n" "")
+ (emacsconf-surround "- Chat: "
+ (org-link-make-string
+ (plist-get talk :webchat-url)
+ (concat "#" (plist-get talk :channel)))
+ "\n" "")
+ "- " (org-link-make-string
+ (plist-get talk :video-url)
+ (if (plist-get talk :video-time)
+
+ "Talk recording (posted soon after the talk starts)"
+ "Recording for live talk (posted in a week or two)")) "\n"
+ "- "
+ (org-link-make-string
+ (plist-get talk :captions-url)
+ (if (plist-get talk :video-time)
+ "Captions (posted soon after the talk starts)"
+ "Captions for live talk (posted in a week or two)"))
+ "\n"
+ "- Email for questions: "
+ (let ((email (or (plist-get talk :public-email)
+ emacsconf-fallback-email)))
+ (org-link-make-string
+ (concat "mailto:"
+ email
+ "?body=&subject="
+ (url-hexify-string
+ (format "%s %s: %s"
+ emacsconf-name
+ emacsconf-year
+ (plist-get talk :title))))
+ email))
+ "\n"
+ (let ((other-files
+ (seq-remove (lambda (o)
+ (string-match "--main\\.\\(vtt\\|webm\\)"))
+ (emacsconf-publish-filter-public-files talk))))
+ (if other-files
+ (concat "- Other files:\n"
+ (mapconcat
+ (lambda (file)
+ (concat " - "
+ (org-link-make-string
+ (format "%s%s/%s"
+ emacsconf-media-base-url
+ (file-name-nondirectory file))
+ (replace-regexp-in-string
+ (concat "^" (regexp-quote (plist-get talk :file-prefix))) ""
+ (file-name-nondirectory file)))))
+ other-files "\n")
+ "\n")
+ ""))
+ (emacsconf-surround "\n" (plist-get talk :intro-note) "\n" "")
+ (emacsconf-surround "\nDescription:\n\n"
+ (when (plist-get talk :org-description)
+ (with-temp-buffer
+ (if (org-kill-is-subtree-p (plist-get talk :org-description))
+ (org-paste-subtree 3 (plist-get talk :org-description))
+ (insert (plist-get talk :org-description)))
+ (buffer-string)))
+ "\n" ""))))
(emacsconf-filter-talks-by-track track info)
"\n")))
(defun emacsconf-publish-schedule-org-for-timezone (timezone &optional info)
- (interactive (list (completing-read "Timezone: " emacsconf-timezones)))
+ (interactive (list (completing-read "Time zone: " emacsconf-timezones)))
(let ((new-filename (expand-file-name
(concat "schedule-"
(replace-regexp-in-string
@@ -228,7 +304,7 @@
(with-temp-file new-filename
(insert
"* " emacsconf-name " " emacsconf-year "\n\nTimes are in "
- (emacsconf-schedule-rename-etc-timezone timezone) " timezone. You can find this file and other calendars at "
+ (emacsconf-schedule-rename-etc-timezone timezone) " time zone. You can find this file and other calendars at "
emacsconf-media-base-url emacsconf-year "/schedules/ .\n\n"
(mapconcat (lambda (track)
(emacsconf-publish-format-track-as-org track timezone info))
@@ -237,7 +313,8 @@
(defun emacsconf-publish-schedule-org-files (&optional info)
(interactive)
- (setq info (or info (emacsconf-publish-prepare-for-display (emacsconf-get-talk-info))))
+ (let ((emacsconf-talk-info-functions (append emacsconf-talk-info-functions (list 'emacsconf-get-abstract-from-wiki))))
+ (setq info (or info (emacsconf-publish-prepare-for-display (emacsconf-get-talk-info)))))
(mapc (lambda (tz) (emacsconf-publish-schedule-org-for-timezone tz info))
(append
emacsconf-timezones
@@ -250,45 +327,54 @@
(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\">Pad</a>" (plist-get o :pad-url))
- "")
- "</td>"
- "<td>" (format "<a href=\"%s\" target=\"_blank\" rel=\"noreferrer\">Chat</a>" (plist-get o :webchat-url))
- ""
- "</td>"
+ (format "<tr id=\"%s\">" (plist-get o :slug))
"<td>" (format-time-string "%-l:%M" (plist-get o :start-time) emacsconf-timezone) "</td>"
+ "<td><strong>"
+ (cond
+ ((not (emacsconf-talk-recorded-p o))
+ (format-time-string "%-l:%M" (plist-get o :start-time) emacsconf-timezone))
+ ((string-match "live" (plist-get o :qa-type))
+ (format-time-string "%-l:%M" (plist-get o :qa-time) emacsconf-timezone))
+ (t ""))
+ "</strong></td>"
"<td>" (format "<a href=\"%s%s/talks/%s\" target=\"_blank\" rel=\"noreferrer\">%s</a>"
emacsconf-base-url
emacsconf-year
(plist-get o :slug)
(plist-get o :slug))
"</td>"
+ "<td>" (if (emacsconf-talk-recorded-p o) (plist-get o :qa-type) "(live talk)") "</td>"
+ (if (plist-get o :bbb-room)
+ (format "<td><button class=\"copy\" data-copy=\"%s\" data-label=\"Copy mod code\">Copy mod code</button></td>"
+ (plist-get o :bbb-mod-code))
+ "<td></td>")
+ (concat "<td>"
+ (if (plist-get o :bbb-room)
+ (format "<a href=\"%s\" target=\"_blank\" rel=\"noreferrer\">Join Q&A</a>" (plist-get o :bbb-room)
+ ""))
+ "</td>")
+ "<td>" (if (plist-get o :pad-url)
+ (format "<a href=\"%s\" target=\"_blank\" rel=\"noreferrer\">Pad</a>" (plist-get o :pad-url))
+ "")
+ "</td>"
+ "<td>" (format "<a href=\"%s\" target=\"_blank\" rel=\"noreferrer\">Chat</a>" (plist-get o :webchat-url))
+ ""
+ "</td>"
+
"<td>" (or (plist-get o :title) "") "</td>"
+ "<td>" (or (plist-get o :speakers) "") (emacsconf-surround " (" (plist-get o :irc) ")" "") "</td>"
"</tr>"))
info
"\n"))
-(defun emacsconf-publish-res-index ()
+(defun emacsconf-publish-backstage-talk-index ()
"Publish BBB room URLs and pad links for volunteer convenience."
(interactive)
(let* ((emacsconf-publishing-phase 'conference)
(info (mapcar (lambda (o)
(append (list
:url (concat "#" (plist-get o :slug)))
- (if (and (string-match "live" (or (plist-get o :q-and-a) ""))
- (plist-get o :bbb-room))
- (append (list
- :qa-link
- (format "<a href=\"%s\" target=\"_blank\" rel=\"noreferrer\">Join Q&A</a>" (plist-get o :bbb-room)))
- o)
- o)))
+ o))
(emacsconf-publish-prepare-for-display (emacsconf-get-talk-info)))))
(mapc
(lambda (track)
@@ -301,6 +387,7 @@
"<html><head><meta charset=\"utf-8\" /><link rel=\"stylesheet\" href=\"style.css\"></head><body>
<div>"
(let ((emacsconf-use-absolute-url t)
+ (emacsconf-schedule-svg-modify-functions '(emacsconf-schedule-svg-color-by-status))
(emacsconf-base-url ""))
(with-temp-buffer
(svg-print (emacsconf-schedule-svg 800 300 info))
@@ -311,17 +398,19 @@
(mapconcat
(lambda (day)
(format
- "<tr><th colspan=\"6\" style=\"text-align: left\">%s</th></tr>
-<tr><th>Q&A</th><th>Pad</th><th>Chat</th><th>Time</th><th>Slug</th><th>Title</th></tr>
+ "<tr><th colspan=\"7\" style=\"text-align: left\">%s</th></tr>
+<tr><th>Talk start</th><th>BBB start</th><th>Talk ID</th><th>Q&A type</th><th>Mod code</th><th>BBB</th><th>Pad</th><th>Chat</th><th>Title</th><th>Speakers</th></tr>
%s"
(car day) (emacsconf-publish-format-res-talks (cdr day))))
(seq-group-by
(lambda (o) (format-time-string "%A, %b %-e" (plist-get o :start-time)))
track-talks)
"\n")
- "</table></body></html>")))
- (with-temp-file (expand-file-name (format "index-%s.html" (plist-get track :id)) emacsconf-res-dir)
- (insert result))
+ "</table></body>"
+ (with-temp-buffer
+ (insert-file-contents (expand-file-name "include-in-index-footer.html" emacsconf-cache-dir))
+ (buffer-string))
+ "</html>")))
(with-temp-file (expand-file-name (format "index-%s.html" (plist-get track :id)) emacsconf-backstage-dir)
(insert result))))
emacsconf-tracks)))
@@ -341,10 +430,10 @@
(list
:source-src
(when (stringp video-file)
- (if (plist-get talk :public)
- (format "%s%s/%s" emacsconf-media-base-url (plist-get talk :conf-year)
- (file-name-nondirectory video-file))
- (file-name-nondirectory video-file)))
+ (if (plist-get talk (if (string-match "--answers" video-file) :qa-public :public))
+ (format "%s%s/%s" emacsconf-media-base-url (plist-get talk :conf-year)
+ (file-name-nondirectory video-file))
+ (file-name-nondirectory video-file)))
:captions
(or
(and (stringp video-file)
@@ -358,9 +447,8 @@
(let ((tracks
(emacsconf-video-subtitle-tracks
(or (plist-get talk :caption-file)
- (concat (replace-regexp-in-string "reencoded\\|original" "main"
- video-base)
- ".vtt"))
+ (emacsconf-talk-file talk "--main.vtt")
+ (emacsconf-talk-file talk "--reencoded.vtt"))
(or (plist-get talk :track-base-url)
(plist-get talk :base-url))
(plist-get talk :files))))
@@ -393,6 +481,10 @@
(and (plist-get talk :backstage)
(plist-get talk :bbb-backstage))
"\">Open backstage BigBlueButton</a></li>" "")
+ (emacsconf-surround "<li>BBB mod code: "
+ (and (plist-get talk :backstage)
+ (plist-get talk :bbb-mod-code))
+ "</li>" "")
(emacsconf-surround "<li><a href=\""
(and (member emacsconf-publishing-phase '(schedule conference))
(plist-get talk :qa-url))
@@ -406,6 +498,11 @@
(concat "<li>" s "</li>"))
(emacsconf-publish-link-file-formats-as-list talk)
"")
+ :youtube-info (if (plist-get talk :youtube-url)
+ (format
+ "<li><a href=\"%s\">View on Youtube</a></li>"
+ (plist-get talk :youtube-url))
+ "")
:toobnix-info (if (plist-get talk :toobnix-url)
(format
"<li><a href=\"%s\">View on Toobnix</a></li>"
@@ -435,7 +532,7 @@
:resources
(emacsconf-replace-plist-in-string
info
- "<div class=\"files resources\"><ul>${links}${other-files}${toobnix-info}</ul></div>"))))
+ "<div class=\"files resources\"><ul>${links}${other-files}${toobnix-info}${youtube-info}</ul></div>"))))
(defun emacsconf-publish-format-public-email (o &optional email)
(format "[%s](mailto:%s?subject=%s)"
@@ -447,9 +544,15 @@
(let ((extra-info (mapconcat #'identity
(delq nil (list
(unless (string= (plist-get o :pronunciation) "nil")
- (emacsconf-surround "Pronunciation: " (plist-get o :pronunciation) ""))
+ (emacsconf-surround "Pronunciation: "
+ (if (string-match "\\[" (or (plist-get o :pronunciation) ""))
+ (org-export-string-as (plist-get o :pronunciation) 'md t)
+ (plist-get o :pronunciation))
+ ""))
(when (plist-get o :irc) (format "IRC: %s" (plist-get o :irc)))
- (plist-get o :public-contact)
+ (if (string-match "\\[" (or (plist-get o :public-contact) ""))
+ (org-export-string-as (plist-get o :public-contact) 'md t)
+ (plist-get o :public-contact))
(when (plist-get o :public-email) (format "<mailto:%s>" (plist-get o :public-email)))))
", ")))
(concat (plist-get o :speakers-with-pronouns)
@@ -501,21 +604,21 @@ ${categories}
(defun emacsconf-publish-talk-p (talk)
"Return non-nil if the talk is ready to be published.
-Talks that are pending review will not be published yet."
+ Talks that are pending review will not be published yet."
(pcase (plist-get talk :status)
('nil nil)
("TODO" nil)
("TO_REVIEW" nil)
- ("TO_ACCEPT" nil)
+ ;; ("TO_ACCEPT" nil)
("CANCELLED" nil)
(_ t)))
(defun emacsconf-publish-talk-pages (emacsconf-info &optional force)
"Populate year/talks/*.md files.
-These should include the nav and schedule files, which will be
-rewritten as needed. After they are generated, they should be all
-right to manually edit to include things like additional
-resources."
+ These should include the nav and schedule files, which will be
+ rewritten as needed. After they are generated, they should be all
+ right to manually edit to include things like additional
+ resources."
(interactive (list (emacsconf-get-talk-info) (> (prefix-numeric-value current-prefix-arg) 1)))
(mapc (lambda (o) (emacsconf-publish-talk-page o force))
(emacsconf-filter-talks emacsconf-info)))
@@ -541,8 +644,10 @@ resources."
(emacsconf-publish-index-card (append
(list
:public 1
- :video-id (concat (plist-get o :slug) "-qanda")
- :toobnix-url nil
+ :video-type "qanda"
+ :video-id (concat "qanda-" (plist-get o :slug))
+ :youtube-url (plist-get o :qa-youtube)
+ :toobnix-url (plist-get o :qa-toobnix)
:captions-edited (plist-get o :qa-captions-edited)
:caption-file (emacsconf-talk-file o "--answers.vtt")
:video-file (emacsconf-talk-file o "--answers.webm")
@@ -559,30 +664,41 @@ resources."
(plist-get track :webchat-url)
(plist-get track :channel))))
-(defvar emacsconf-publish-include-pads nil "When non-nil, include Etherpad info.")
-
(defun emacsconf-publish-format-talk-schedule-info (o)
"Format schedule information for O."
(let ((friendly (concat "/" emacsconf-year "/talks/" (plist-get o :slug) ))
- (timestamp (org-timestamp-from-string (plist-get o :scheduled))))
+ (timestamp (org-timestamp-from-string (plist-get o :scheduled)))
+ (talk-p (emacsconf-publish-talk-p o)))
(emacsconf-replace-plist-in-string
(append o
(list
:format
- (concat (or (plist-get o :video-time)
- (plist-get o :time))
- "-min talk; Q&A: "
- (pcase (plist-get o :qa-type)
- ("none" "ask questions via Etherpad/IRC; we'll e-mail the speaker and post answers on this wiki page after the conference")
- ("live" "BigBlueButton conference room")
- ("pad" "Etherpad")
- ("irc" "IRC")
- (_ (plist-get o :qa-type)))
- (emacsconf-surround " <" (and (member emacsconf-publishing-phase '(schedule conference))
- (plist-get o :qa-url)) ">" ""))
+ (if talk-p
+ (concat (or (plist-get o :video-time)
+ (plist-get o :time))
+ "-min talk ; Q&A: "
+ (pcase (plist-get o :qa-type)
+ ("none" "ask questions via Etherpad/IRC; we'll e-mail the speaker and post answers on this wiki page after the conference")
+ ("live" "BigBlueButton conference room")
+ ("pad" "Etherpad")
+ ("irc" "IRC")
+ (_ (plist-get o :qa-type)))
+ (emacsconf-surround " <" (and (member emacsconf-publishing-phase '(schedule conference))
+ (plist-get o :qa-url)) ">" "")
+ (if (string= (plist-get o :qa-type) "pad")
+ ""
+ (format " Etherpad: <%s>"
+ (plist-get o :pad-url)
+ )
+ ))
+ (concat (or (plist-get o :video-time)
+ (plist-get o :time))
+ (if (string= (plist-get o :status) "CANCELLED")
+ "-min talk cancelled"
+ "-min talk")))
:pad-info
- (if (and emacsconf-publish-include-pads (not (and (member emacsconf-publishing-phase '(schedule conference))
- (string= (plist-get o :qa-type) "etherpad"))))
+ (if (and talk-p emacsconf-publish-include-pads (not (and (member emacsconf-publishing-phase '(schedule conference))
+ (string= (plist-get o :qa-type) "etherpad"))))
(format "Etherpad: <https://pad.emacsconf.org/%s-%s> \n" emacsconf-year (plist-get o :slug))
"")
:irc-info
@@ -608,7 +724,7 @@ resources."
(org-timestamp-split-range
(org-timestamp-from-string (plist-get o :scheduled))))))
(format
- "<div>Times in different timezones:</div><div class=\"times\" start=\"%s\" end=\"%s\"><div class=\"conf-time\">%s</div><div class=\"others\"><div>which is the same as:</div>%s</div></div><div><strong><a href=\"/%s/watch/%s/\">Find out how to watch and participate</a></strong></div>"
+ "<div>Times in different time zones:</div><div class=\"times\" start=\"%s\" end=\"%s\"><div class=\"conf-time\">%s</div><div class=\"others\"><div>which is the same as:</div>%s</div></div><div><strong><a href=\"/%s/watch/%s/\">Find out how to watch and participate</a></strong></div>"
(format-time-string "%Y-%m-%dT%H:%M:%SZ" start t)
(format-time-string "%Y-%m-%dT%H:%M:%SZ" end t)
(emacsconf-timezone-string o emacsconf-timezone)
@@ -631,7 +747,7 @@ ${alternate-apac-info}\n")))
(and (string= (plist-get talk :public-email) "t")
(plist-get talk :email))
(plist-get talk :public-email)
- "emacsconf-org-private@gnu.org"))))
+ emacsconf-fallback-email))))
(defun emacsconf-publish-captions-in-wiki (talk)
"Copy the captions file."
@@ -659,7 +775,7 @@ ${alternate-apac-info}\n")))
"\nThe following image shows where the talk is in the schedule for "
(format-time-string "%a %Y-%m-%d" (plist-get talk :start-time) emacsconf-timezone) ". Solid lines show talks with Q&A via BigBlueButton. Dashed lines show talks with Q&A via IRC or Etherpad."
(format "<div class=\"schedule-in-context schedule-svg-container\" data-slug=\"%s\">\n" (plist-get talk :slug))
- (let* ((width 800) (height 150)
+ (let* ((width 700) (height 150)
(talk-date (format-time-string "%Y-%m-%d" (plist-get talk :start-time) emacsconf-timezone))
(start (date-to-time (concat talk-date "T" emacsconf-schedule-start-time emacsconf-timezone-offset)))
(end (date-to-time (concat talk-date "T" emacsconf-schedule-end-time emacsconf-timezone-offset)))
@@ -694,13 +810,14 @@ This includes the intro note, the schedule, and talk resources."
(setq info (or info (emacsconf-publish-prepare-for-display (emacsconf-get-talk-info))))
(with-temp-file (expand-file-name (format "%s-before.md" (plist-get talk :slug))
(expand-file-name "info" (expand-file-name emacsconf-year emacsconf-directory)))
-
+ (hack-dir-local-variables-non-file-buffer)
(insert "<!-- Automatically generated by emacsconf-publish-before-page -->\n")
(insert (emacsconf-surround "" (plist-get talk :intro-note) "\n\n" ""))
(let ((is-live (emacsconf-talk-live-p talk)))
(when is-live (emacsconf-publish-captions-in-wiki talk))
- (when (member emacsconf-publishing-phase '(schedule conference))
- (insert (emacsconf-publish-format-talk-schedule-image talk info)))
+ (when (emacsconf-publish-talk-p talk)
+ (when (member emacsconf-publishing-phase '(schedule conference))
+ (insert (emacsconf-publish-format-talk-schedule-image talk info))))
(insert (emacsconf-publish-format-talk-schedule-info talk) "\n\n")
(insert
(if (plist-get talk :public) (emacsconf-wiki-talk-resources talk) "")
@@ -710,13 +827,14 @@ This includes the intro note, the schedule, and talk resources."
(defun emacsconf-format-transcript-from-list (subtitles video-id &optional lang)
"Return subtitle directives for SUBTITLES."
- (when (stringp subtitles) (setq subtitles (subed-parse-file subtitles)))
+ (let ((subed-sanitize-functions nil))
+ (when (stringp subtitles) (setq subtitles (subed-parse-file subtitles))))
(mapconcat
(lambda (sub)
(let ((msecs (elt sub 1)))
(concat
(if (and (elt sub 4) (not (string= (elt sub 4) "")))
- (format "\n[[!template new=\"1\" text=\"\"\"%s\"\"\" start=\"%s\" video=\"%s\" id=\"subtitle\"%s]]\n\n"
+ (format "\n<div class=\"transcript-heading\">[[!template new=\"1\" text=\"\"\"%s\"\"\" start=\"%s\" video=\"%s\" id=\"subtitle\"%s]]</div>"
(string-trim (replace-regexp-in-string "^NOTE[ \n]" "" (elt sub 4)))
(concat (format-seconds "%02h:%02m:%02s" (/ (floor msecs) 1000))
"." (format "%03d" (mod (floor msecs) 1000)))
@@ -725,8 +843,11 @@ This includes the intro note, the schedule, and talk resources."
"")
(format
"[[!template text=\"\"\"%s\"\"\" start=\"%s\" video=\"%s\" id=\"subtitle\"%s]]"
- (replace-regexp-in-string "^#" "\\\\#"
- (replace-regexp-in-string "\"" "&quot;" (elt sub 3)))
+ (replace-regexp-in-string
+ "^#" "\\\\#"
+ (replace-regexp-in-string
+ "*" "\\\\*"
+ (replace-regexp-in-string "\"" "&quot;" (elt sub 3))))
(concat (format-seconds "%02h:%02m:%02s" (/ (floor msecs) 1000))
"." (format "%03d" (mod (floor msecs) 1000)))
video-id
@@ -736,7 +857,9 @@ This includes the intro note, the schedule, and talk resources."
(defun emacsconf-publish-format-transcript (talk &optional video-id lang title)
"Format the transcript for TALK, adding paragraph markers when possible."
(require 'subed)
- (let* ((subtitles
+ (setq video-id (or video-id "mainVideo"))
+ (let* ((subed-sanitize-functions nil)
+ (subtitles
(subed-parse-file (if lang
(format "%s_%s.vtt"
(file-name-sans-extension
@@ -744,14 +867,14 @@ This includes the intro note, the schedule, and talk resources."
lang)
(plist-get talk :caption-file)))))
(if subtitles
- (format "<a name=\"%s-%s-transcript%s\"></a>
-# %s%s
+ (format "<div class=\"transcript%s\"><a name=\"%s-%s-transcript%s\"></a><h1>%s%s</h1>
%s
-"
+</div>"
+ (if video-id (concat " transcript-" video-id) "")
(plist-get talk :slug)
- (or video-id "mainVideo")
+ video-id
(emacsconf-surround "-" lang "" "")
(if lang (assoc-default lang emacsconf-publish-subtitle-languages) (or title "Transcript"))
(if (emacsconf-captions-edited-p (plist-get talk :caption-file))
@@ -800,12 +923,13 @@ This includes captions, contact, and an invitation to participate."
;; Contact information
(with-temp-file (expand-file-name (format "%s-after.md" (plist-get talk :slug))
(expand-file-name "info" (expand-file-name emacsconf-year emacsconf-directory)))
+ (hack-dir-local-variables-non-file-buffer)
(insert
"<!-- Automatically generated by emacsconf-publish-after-page -->\n"
"\n\n"
;; main transcript
(if (plist-get talk :public) (emacsconf-publish-format-captions talk) "")
- (if (emacsconf-talk-file talk "--answers.vtt")
+ (if (and (plist-get talk :qa-public) (emacsconf-talk-file talk "--answers.vtt"))
(emacsconf-publish-format-transcript
(append
(list :chapter-file (emacsconf-talk-file talk "--answers--chapters.vtt")
@@ -834,6 +958,12 @@ This includes captions, contact, and an invitation to participate."
(plist-get b :start-time)) t)
(t nil)))
+(defun emacsconf-publish-cancelled-nav-page (talk)
+ (with-temp-file (expand-file-name (format "%s-nav.md" (plist-get talk :slug))
+ (expand-file-name "info" (expand-file-name emacsconf-year emacsconf-directory)))
+ (insert "\n<div class=\"talk-nav\">
+Back to the [[talks]] \n</div>")))
+
(defun emacsconf-publish-nav-pages (&optional talks)
"Generate links to the next and previous talks.
During the schedule and conference phase, the talks are sorted by time.
@@ -898,7 +1028,10 @@ Back to the [[talks]] \n"
(emacsconf-publish-with-wiki-change
(mapc (lambda (o)
(emacsconf-publish-before-page o info))
- info)))
+ info)
+ (mapc (lambda (o) (emacsconf-publish-before-page o info))
+ (seq-filter (lambda (o) (string= (plist-get o :status) "CANCELLED"))
+ (emacsconf-filter-talks info)))))
(defun emacsconf-generate-main-schedule-with-tracks (&optional info)
(interactive (list nil))
@@ -933,7 +1066,7 @@ Back to the [[talks]] \n"
Times below are in %{timezone} (GMT${gmt-offset}). If you have Javascript enabled, clicking on talk pages should include times in your computer's local time setting.
-You can also get this schedule as iCalendar files: ${icals}. Importing that into your calendar should translate things into your local timezone. Alternatively, you can use these timezone-translated Org files: <${schedule-directory}>")))
+You can also get this schedule as iCalendar files: ${icals}. Importing that into your calendar should translate things into your local time zone. Alternatively, you can use these time-zone-translated Org files: <${schedule-directory}>")))
;; By track
(let* ((by-day (mapcar (lambda (o))
(seq-group-by (lambda (o)
@@ -1012,8 +1145,12 @@ You can also get this schedule as iCalendar files: ${icals}. Importing that into
"Return a list with the schedule for INFO.
Entries are sorted chronologically, with different tracks interleaved."
(setq info (or info (emacsconf-get-talk-info)))
- (let* ((by-day (emacsconf-by-day (emacsconf-publish-prepare-for-display info)))
- (cancelled (seq-filter (lambda (o) (string= (plist-get o :status) "CANCELLED")) info))
+ (let* ((by-day (emacsconf-by-day (seq-remove (lambda (o)
+ (or
+ (not (plist-get o :scheduled))
+ (member (plist-get o :status) '("TODO" "TO_REVIEW" "TO_ACCEPT"))))
+ (emacsconf-publish-prepare-for-display info))))
+ (cancelled (seq-filter (lambda (o) (string= (plist-get o :status) "CANCELLED")) (emacsconf-get-talk-info)))
(dates (seq-map (lambda (o) (plist-get (cadr o) :start-time)) by-day))
(links (mapcar (lambda (o)
(format "<a href=\"#date-%s\">%s</a>"
@@ -1056,61 +1193,59 @@ Entries are sorted chronologically, with different tracks interleaved."
"")
)))
-(defun emacsconf-publish-schedule (&optional info)
- "Generate the schedule or program."
- (interactive)
- (emacsconf-publish-schedule-svg-snippets)
- (setq info (or info (emacsconf-publish-prepare-for-display info)))
- (with-temp-file (expand-file-name "schedule-details.md"
- (expand-file-name emacsconf-year emacsconf-directory))
- (when (member emacsconf-publishing-phase '(schedule conference))
- (insert
- (emacsconf-replace-plist-in-string
- (list
- :timezone emacsconf-timezone
- :gmt-offset emacsconf-timezone-offset
- :alternative-timezones
- (string-join (emacsconf-timezone-strings
- (format "<%s %s-%s>"
- emacsconf-date
- (plist-get (car emacsconf-tracks) :start)
- (plist-get (car emacsconf-tracks) :end))
- nil "~%-l:%M %p")
- " / ")
- :icals
- (concat
- (format "<a href=\"%s%s/%s.ics\">%s.ics</a> - "
- emacsconf-media-base-url
- emacsconf-year
- emacsconf-id
- emacsconf-id)
- (mapconcat (lambda (track)
- (format "<a href=\"%s%s/%s-%s.ics\">%s-%s.ics</a>"
- emacsconf-media-base-url
- emacsconf-year
- emacsconf-id
- (plist-get track :id)
- emacsconf-id
- (plist-get track :id)))
- emacsconf-tracks " - "))
- :schedule-directory
- (concat emacsconf-media-base-url emacsconf-year "/schedules/"))
- "The conference is from ${alternative-timezones}.
-
-Times below are in ${timezone} (GMT${gmt-offset}). If you have Javascript enabled, clicking on talk pages should include times in your computer's local time setting.
-
-You can also get this schedule as iCalendar files: ${icals}. Importing that into your calendar should translate things into your local timezone. Alternatively, you can use these timezone-translated Org files: <${schedule-directory}>
-
-")))
- (insert
- (if (member emacsconf-publishing-phase '(cfp program))
- (let ((sorted (emacsconf-publish-prepare-for-display (or info (emacsconf-get-talk-info)))))
- (mapconcat
- (lambda (track)
- (concat
- "Jump to: "
- ;; links to other tracks
- (string-join (seq-keep (lambda (track-link)
+(defun emacsconf-publish-schedule-with-times (&optional info)
+ (insert
+ (emacsconf-replace-plist-in-string
+ (list
+ :timezone emacsconf-timezone
+ :year emacsconf-year
+ :gmt-offset emacsconf-timezone-offset
+ :alternative-timezones
+ (string-join (emacsconf-timezone-strings
+ (format "<%s %s-%s>"
+ emacsconf-date
+ (plist-get (car emacsconf-tracks) :start)
+ (plist-get (car emacsconf-tracks) :end))
+ nil "~%-l:%M %p")
+ " / ")
+ :icals
+ (concat
+ (format "<a href=\"%s%s/%s.ics\">%s.ics</a> - "
+ emacsconf-media-base-url
+ emacsconf-year
+ emacsconf-id
+ emacsconf-id)
+ (mapconcat (lambda (track)
+ (format "<a href=\"%s%s/%s-%s.ics\">%s-%s.ics</a>"
+ emacsconf-media-base-url
+ emacsconf-year
+ emacsconf-id
+ (plist-get track :id)
+ emacsconf-id
+ (plist-get track :id)))
+ emacsconf-tracks " - "))
+ :schedule-directory
+ (concat emacsconf-media-base-url emacsconf-year "/schedules/"))
+ "Times below are in ${timezone} (GMT${gmt-offset}). If you have Javascript enabled, clicking on talk pages should include times in your computer's local time setting.
+
+[[!inline pages=\"internal(${year}/schedule-image)\" raw=\"yes\"]]
+
+The conference is from ${alternative-timezones}.
+
+You can also get this schedule as iCalendar files: ${icals}. Importing that into your calendar should translate things into your local time zone. Alternatively, you can use these time-zone-translated Org files: <${schedule-directory}>
+
+")
+ (emacsconf-publish-format-interleaved-schedule info)))
+
+(defun emacsconf-publish-program-without-times (&optional info)
+ (insert
+ (let ((sorted (emacsconf-publish-prepare-for-display (or info (emacsconf-get-talk-info)))))
+ (mapconcat
+ (lambda (track)
+ (concat
+ "Jump to: "
+ ;; links to other tracks
+ (string-join (seq-keep (lambda (track-link)
(unless (string= (plist-get track-link :id)
(plist-get track :id))
(format "<a href=\"#%s\">%s</a>"
@@ -1118,28 +1253,42 @@ You can also get this schedule as iCalendar files: ${icals}. Importing that into
(plist-get track-link :name))))
emacsconf-tracks)
" | ")
- "\n\n"
- (let ((track-talks (seq-filter (lambda (o) (string= (plist-get o :track)
+ "\n\n"
+ (let ((track-talks (seq-filter (lambda (o) (string= (plist-get o :track)
(plist-get track :name)))
- sorted)))
- (format
- "<h1 id=\"%s\" class=\"sched-track %s\">%s (%d talks)</h1>\n%s"
- (plist-get track :id)
- (plist-get track :name)
- (plist-get track :name)
- (length track-talks)
- (emacsconf-publish-format-main-schedule track-talks)))))
- emacsconf-tracks "\n\n"))
- (emacsconf-publish-format-interleaved-schedule info))))
- (when (member emacsconf-publishing-phase '(cfp program))
- (with-temp-file (expand-file-name
+ sorted)))
+ (format
+ "<h1 id=\"%s\" class=\"sched-track %s\">%s (%d talk%s)</h1>\n%s"
+ (plist-get track :id)
+ (plist-get track :name)
+ (plist-get track :name)
+ (length track-talks)
+ (if (= (length track-talks) 1) "" "s")
+ (emacsconf-publish-format-main-schedule track-talks)))))
+ emacsconf-tracks "\n\n"))))
+
+(defun emacsconf-publish-schedule (&optional info)
+ "Generate the schedule or program."
+ (interactive)
+ (unless (eq emacsconf-publishing-phase 'cfp) (emacsconf-publish-schedule-svg-snippets))
+ (setq info (or info (emacsconf-publish-prepare-for-display info)))
+ (pcase emacsconf-publishing-phase
+ ((or 'schedule 'conference)
+ (with-temp-file (expand-file-name "schedule-details.md"
+ (expand-file-name emacsconf-year emacsconf-directory))
+ (emacsconf-publish-schedule-with-times info)))
+ ((or 'cfp 'program 'harvest 'resources)
+ (with-temp-file (expand-file-name "schedule-details.md"
+ (expand-file-name emacsconf-year emacsconf-directory))
+ (emacsconf-publish-program-without-times info))
+ (with-temp-file (expand-file-name
"draft-schedule.md"
(expand-file-name emacsconf-year emacsconf-directory))
(insert
"[[!sidebar content=\"\"]]\n\n"
"This is a *DRAFT* schedule.\n"
(let ((emacsconf-publishing-phase 'schedule))
- (emacsconf-publish-format-interleaved-schedule info))))))
+ (emacsconf-publish-format-interleaved-schedule info)))))))
(defun emacsconf-format-talk-link (talk)
(and talk (if (plist-get talk :slug)
@@ -1174,7 +1323,8 @@ You can also get this schedule as iCalendar files: ${icals}. Importing that into
(pcase emacsconf-publishing-phase
('program
(list
- :time (plist-get o :time)))
+ :time (plist-get o :time)
+ :note (plist-get o :sched-note)))
((or 'schedule 'conference)
(list
:status (pcase (plist-get o :status)
@@ -1186,7 +1336,9 @@ You can also get this schedule as iCalendar files: ${icals}. Importing that into
:time (plist-get o :time)
:q-and-a (plist-get o :qa-link)
:note (plist-get o :sched-note)
- :pad (and emacsconf-publish-include-pads (plist-get o :pad-url))
+ :pad (and emacsconf-publish-include-pads
+ (not (string= "pad" (plist-get o :qa-type)))
+ (plist-get o :pad-url))
:startutc (format-time-string "%FT%T%z" (plist-get o :start-time) t)
:endutc (format-time-string "%FT%T%z" (plist-get o :end-time) t)
:start (format-time-string "%-l:%M" (plist-get o :start-time) emacsconf-timezone)
@@ -1225,6 +1377,8 @@ You can also get this schedule as iCalendar files: ${icals}. Importing that into
(or (plist-get o :toobnix-url)
(plist-get o :video-file)))
"video posted")
+ (when (plist-get o :qa-public)
+ "Q&A posted")
(emacsconf-surround "video: " (plist-get o :video-duration) "" nil)
(emacsconf-surround "answers: " (and (plist-get o :qa-public)
(plist-get o :qa-video-duration))
@@ -1283,7 +1437,7 @@ Use FILES as the file list, or look in the cache directory."
(assoc-default "TO_ASSIGN" by-status)
files
"to be captioned, waiting for volunteers"
- "<p>You can e-mail <a href=\"mailto:sacha@sachachua.com\">sacha@sachachua.com</a> to call dibs on editing the captions for one of these talks. We use OpenAI Whisper to provide auto-generated VTT that you can use as a starting point, but you can also write the captions from scratch if you like. The starting point for the main video ends in --main.vtt. If you're writing the captions from scratch, you can choose to include timing information, or we can probably figure them out afterwards with a forced alignment tool. More info: <a href=\"https://emacsconf.org/captioning/\">captioning tips</a></p>"
+ "<p>You can e-mail <a href=\"mailto:sacha@sachachua.com\">sacha@sachachua.com</a> to call dibs on editing the captions for one of these talks. We use OpenAI Whisper to provide auto-generated VTT that you can use as a starting point, but you can also write the captions from scratch if you like. The VTT file has timing information and the TXT file has the plain text; you can work with either. If you're writing the captions from scratch, you can choose to include timing information, or we can figure them out afterwards with a forced alignment tool. More info: <a href=\"https://emacsconf.org/captioning/\">captioning tips</a></p>"
(lambda (f)
(append
f
@@ -1334,10 +1488,15 @@ If MODIFY-FUNC is specified, use it to modify the talk."
f))))
talks "\n")))
-(defun emacsconf-publish-backstage-index (&optional filename)
+(defun emacsconf-publish-backstage-index (&optional filename dest)
"Render the backstage index to FILENAME."
- (interactive)
- (setq filename (or filename (expand-file-name "index.html" emacsconf-backstage-dir)))
+ (interactive (list nil (if current-prefix-arg
+ emacsconf-cache-dir
+ emacsconf-backstage-dir)))
+ (setq dest (or dest
+ emacsconf-cache-dir))
+ (emacsconf-current-org-notebook-refresh-schedule)
+ (setq filename (or filename (expand-file-name "index.html" dest)))
(let ((info (or emacsconf-schedule-draft (emacsconf-publish-prepare-for-display (emacsconf-get-talk-info)))))
(with-temp-file filename
(let* ((talks
@@ -1345,15 +1504,15 @@ If MODIFY-FUNC is specified, use it to modify the talk."
(lambda (o) (append
(list :captions-edited t
:backstage t) o))
- (emacsconf-filter-talks info)))
+ (emacsconf-filter-talks info)))
(by-status (seq-group-by (lambda (o) (plist-get o :status)) talks))
- (files (directory-files emacsconf-backstage-dir)))
+ (files (directory-files dest))) ;emacsconf-backstage-dir very sislow right now
(insert
"<html><head><meta charset=\"UTF-8\"><link rel=\"stylesheet\" href=\"/style.css\" /></head><body>"
(if (file-exists-p (expand-file-name "include-in-index.html" emacsconf-cache-dir))
(with-temp-buffer (insert-file-contents (expand-file-name "include-in-index.html" emacsconf-cache-dir)) (buffer-string))
"")
- "<p>Schedule by status: (gray: waiting, light yellow: processing, yellow: to assign, light green: captioning, green: to check, light blue: captioned and ready)<br />Updated by conf.org and the wiki repository</br />"
+ "<p>Schedule by status: (gray: waiting, light yellow: processing, yellow: to assign, light blue: captioning, light green: to check, green: captioned and ready)<br />Updated by conf.org and the wiki repository</br />"
(let* ((emacsconf-schedule-svg-modify-functions '(emacsconf-schedule-svg-color-by-status))
(img (emacsconf-schedule-svg 800 200 info)))
(with-temp-buffer
@@ -1380,7 +1539,7 @@ If MODIFY-FUNC is specified, use it to modify the talk."
(lambda (status)
(format "<li>%s - %d talk(s), %d minutes: %s</li>"
(if (string= status "TO_ASSIGN")
- "TO_ASSIGN (waiting for captioning volunteers)"
+ "<strong>TO_ASSIGN (waiting for captioning volunteers)</strong>"
status)
(length (assoc-default status by-status))
(emacsconf-sum '(:video-time :time) (assoc-default status by-status))
@@ -1392,7 +1551,7 @@ If MODIFY-FUNC is specified, use it to modify the talk."
", ")))
(pcase emacsconf-publishing-phase
((or 'program 'schedule 'conference)
- '("WAITING_FOR_PREREC" "PROCESSING" "TO_ASSIGN" "TO_CAPTION" "TO_CHECK" "TO_STREAM"))
+ '("TO_CONFIRM" "WAITING_FOR_PREREC" "PROCESSING" "TO_ASSIGN" "TO_CAPTION" "TO_CHECK" "TO_STREAM" "TO_ARCHIVE" "TO_REVIEW_QA"))
((or 'harvest 'resources)
'("TO_ARCHIVE" "TO_REVIEW_QA" "TO_INDEX_QA" "TO_CAPTION_QA" "DONE")))
"")
@@ -1407,7 +1566,7 @@ If MODIFY-FUNC is specified, use it to modify the talk."
", ")
"</div>"
(pcase emacsconf-publishing-phase
- ((or 'program 'schedule 'conference)
+ ((or 'program 'schedule 'conference 'cfp)
(concat
(emacsconf-publish-backstage-list
(append
@@ -1428,7 +1587,11 @@ If MODIFY-FUNC is specified, use it to modify the talk."
files
"ready to be streamed")
(emacsconf-publish-backstage-list
- (assoc-default "WAITING_FOR_PREREC" by-status) files
+ (seq-sort
+ #'emacsconf-sort-by-scheduled
+ (append (assoc-default "WAITING_FOR_PREREC" by-status)
+ (assoc-default "TO_CONFIRM" by-status)))
+ files
"we're waiting for"
"Speakers might submit these, do them live, or cancel the talks.")))
((or 'harvest 'resources)
@@ -1503,7 +1666,10 @@ answers without needing to listen to everything again. You can see <a href=\"htt
(if (file-exists-p (expand-file-name "include-in-index-footer.html" emacsconf-cache-dir))
(with-temp-buffer (insert-file-contents (expand-file-name "include-in-index-footer.html" emacsconf-cache-dir)) (buffer-string))
"")
- "</body></html>")))))
+ "</body></html>"))))
+ (when (and (called-interactively-p 'any)
+ (eq (window-system) 'x))
+ (emacsconf-backstage-web)))
(defun emacsconf-publish-filter-public-files (talk &optional selector files)
"Return files that are okay to post publicly for TALK."
@@ -1517,7 +1683,7 @@ answers without needing to listen to everything again. You can see <a href=\"htt
;; further tests
(pcase f
((rx (seq "--"
- (or "reencoded" "normalized" "final" "old" "bbb")))
+ (or "reencoded" "normalized" "final" "old" "bbb" "backstage" "pad" "silences")))
nil)
((rx ".diff") nil)
((rx "--original")
@@ -1541,11 +1707,11 @@ answers without needing to listen to everything again. You can see <a href=\"htt
files)))
(defun emacsconf-publish-public-index-for-talk (o files)
- (format "<li><div class=\"title\"><a name=\"%s\" href=\"%s\">%s</a></div></div><div class=\"speakers\">%s</div>%s</li>%s"
+ (format "<li><div class=\"title\"><a name=\"%s\" href=\"%s\">%s</a></div></div>%s%s</li>%s"
(plist-get o :slug)
(plist-get o :absolute-url)
(plist-get o :title)
- (plist-get o :speakers)
+ (emacsconf-surround "<div class=\"speakers\">" (plist-get o :speakers) "</div>" "")
(emacsconf-publish-index-card
(append (list :files
(seq-remove (lambda (f) (string-match "--answers" f))
@@ -1570,7 +1736,8 @@ answers without needing to listen to everything again. You can see <a href=\"htt
"\">Play recording from BigBlueButton</a></li>" ""))))
o))
(if (or (emacsconf-talk-file o "--answers.webm")
- (emacsconf-talk-file o "--answers.opus"))
+ (emacsconf-talk-file o "--answers.opus")
+ (emacsconf-get-preferred-video (concat (plist-get o :file-prefix) "--answers")))
(format "<li><div class=\"title\"><a href=\"%s\">Q&amp;A for %s</a></div>%s</li>"
(plist-get o :absolute-url)
(plist-get o :title)
@@ -1617,7 +1784,7 @@ answers without needing to listen to everything again. You can see <a href=\"htt
""))
"<html><head><meta charset=\"utf-8\" /></head><link rel=\"stylesheet\" href=\"/style.css\" /><body>
<h1>${conf-name} ${year}</h1>
-<div class=\"m3u\"><a href=\"index.m3u\">M3U playlist for playing in MPV and other players</a></div>
+<div class=\"m3u\"><a href=\"index.m3u\">M3U playlist for playing in MPV and other players</a> | <a href=\"schedules/\">Schedules</a></div>
<div>Quick links: ${quick-links}</div>
<ol class=\"videos\">${videos}</ol>
${include}
@@ -1646,7 +1813,7 @@ ${include}
:links
(unless (eq emacsconf-publishing-phase 'resources)
(emacsconf-surround "<li><a href=\""
- (plist-get o :bbb-rec)
+ (plist-get f :bbb-rec)
"\">Play recording from BigBlueButton</a></li>" ""))
:files
@@ -1706,46 +1873,65 @@ ${include}
(defun emacsconf-video-subtitle-tracks (filename track-base-url &optional files)
(setq files (or files (directory-files emacsconf-cache-dir)))
- (concat
- (if (member (file-name-nondirectory filename) files)
- (format "<track label=\"English\" kind=\"captions\" srclang=\"en\" src=\"%s\" default />"
- (concat (or track-base-url "") (file-name-nondirectory filename)))
- "")
- (mapconcat
- (lambda (lang)
- (let ((lang-file (concat (file-name-sans-extension filename) "_" (car lang) "." (file-name-extension filename))))
- (if (member lang-file files)
- (format "<track label=\"%s\" kind=\"captions\" srclang=\"%s\" src=\"%s\" />"
- (cdr lang)
- (car lang)
- (concat (or track-base-url "") (file-name-nondirectory lang-file)))
- "")))
- emacsconf-publish-subtitle-languages
- "")))
+ (if filename
+ (concat
+ (if (member (file-name-nondirectory filename) files)
+ (format "<track label=\"English\" kind=\"captions\" srclang=\"en\" src=\"%s\" default />"
+ (concat (or track-base-url "") (file-name-nondirectory filename)))
+ "")
+ (mapconcat
+ (lambda (lang)
+ (let ((lang-file (concat (file-name-sans-extension filename) "_" (car lang) "." (file-name-extension filename))))
+ (if (member lang-file files)
+ (format "<track label=\"%s\" kind=\"captions\" srclang=\"%s\" src=\"%s\" />"
+ (cdr lang)
+ (car lang)
+ (concat (or track-base-url "") (file-name-nondirectory lang-file)))
+ "")))
+ emacsconf-publish-subtitle-languages
+ ""))
+ ""))
(defun emacsconf-publish-link-file-formats (file-prefix)
(string-join (emacsconf-publish-link-file-formats-as-list file-prefix) " "))
+(defun emacsconf-publish-file-description (talk file)
+ (let ((cache-file (expand-file-name (file-name-nondirectory file) emacsconf-cache-dir)))
+ (concat
+ (replace-regexp-in-string (concat "^" (regexp-quote (plist-get talk :file-prefix))) "" (file-name-nondirectory file))
+ (if
+ (and
+ (file-exists-p cache-file)
+ (> (file-attribute-size (file-attributes cache-file)) 1000000))
+ (format " (%sB)" (file-size-human-readable (file-attribute-size (file-attributes cache-file))))
+ ""))))
+
(defun emacsconf-publish-link-file-formats-as-list (talk)
- (seq-map
- (lambda (file)
- (let ((cache-file (expand-file-name (file-name-nondirectory file) emacsconf-cache-dir)))
- (format "<a href=\"%s%s\">Download %s%s</a>%s"
- (or (plist-get talk :base-url) "")
- (file-name-nondirectory file)
- (replace-regexp-in-string (concat "^" (regexp-quote (plist-get talk :file-prefix))) "" (file-name-nondirectory file))
- (if (and (file-exists-p cache-file)
- (> (file-attribute-size (file-attributes cache-file)) 1000000))
- (format " (%sB)" (file-size-human-readable (file-attribute-size (file-attributes cache-file))))
- "")
- (if (and (string-match "--\\(main\\|answers\\)\\.vtt" file)
- (not (emacsconf-captions-edited-p (expand-file-name file emacsconf-cache-dir))))
- " (unedited)"
- ""))))
- (or (plist-get talk :files)
- (if (plist-get talk :backstage)
- (emacsconf-publish-talk-files talk)
- (emacsconf-publish-filter-public-files talk)))))
+ (let ((public-files (emacsconf-publish-filter-public-files talk)))
+ (seq-map
+ (lambda (file)
+ (let ((cache-file (expand-file-name (file-name-nondirectory file) emacsconf-cache-dir)))
+ (format "<a href=\"%s%s\">Download %s%s</a>%s%s"
+ (or (plist-get talk :base-url) "")
+ (file-name-nondirectory file)
+ (replace-regexp-in-string (concat "^" (regexp-quote (plist-get talk :file-prefix))) "" (file-name-nondirectory file))
+ (if (and (file-exists-p cache-file)
+ (> (file-attribute-size (file-attributes cache-file)) 1000000))
+ (format " (%sB)" (file-size-human-readable (file-attribute-size (file-attributes cache-file))))
+ "")
+ (if (and (string-match "--\\(main\\|answers\\)\\.vtt" file)
+ (not (emacsconf-captions-edited-p (expand-file-name file emacsconf-cache-dir))))
+ " (unedited)"
+ "")
+ (if (and (plist-get talk :backstage)
+ (not (member file public-files)))
+ " (backstage)"
+ "")
+ )))
+ (or (plist-get talk :files)
+ (if (plist-get talk :backstage)
+ (emacsconf-publish-talk-files talk)
+ public-files)))))
(defun emacsconf-publish-talks-json ()
"Return JSON format with a subset of talk information."
@@ -1765,6 +1951,7 @@ ${include}
'(:slug :title :speakers :pronouns :pronunciation :url :track :file-prefix
:qa-url
:qa-type
+ :live
:qa-backstage-url))))
(emacsconf-filter-talks (emacsconf-get-talk-info)))
:tracks
@@ -2016,6 +2203,7 @@ ${include}
(autoload 'subed-parse-file "subed-common")
(defun emacsconf-publish-video-description (talk &optional copy skip-title)
(interactive (list (emacsconf-complete-talk-info) t))
+ (when (stringp talk) (setq talk (emacsconf-resolve-talk talk)))
(let ((chapters (subed-parse-file
(expand-file-name
(concat
@@ -2050,15 +2238,85 @@ ${chapters}You can view this and other resources using free/libre software at ${
This video is available under the terms of the Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.")))
(if copy (kill-new result))
result))
+
+(defun emacsconf-publish-youtube-step-through-publishing-talk (talk)
+ (interactive (list (emacsconf-complete-talk-info
+ (seq-remove
+ (lambda (talk)
+ (or (not (plist-get talk :video-file))
+ (plist-get talk :youtube-url)))
+ (emacsconf-get-talk-info)))))
+ (kill-new (plist-get talk :video-file))
+ (y-or-n-p (format "Video: %s - create video and upload this filename. Done?" (plist-get talk :video-file)))
+ (kill-new (emacsconf-publish-video-description talk t))
+ (y-or-n-p "Copied description. Paste into description, move first line to title, add to playlist. Done?")
+ (when (emacsconf-talk-file talk "--main.vtt")
+ (kill-new (emacsconf-talk-file talk "--main.vtt"))
+ (y-or-n-p (format "Captions: %s. Add to video elements. Done?" (emacsconf-talk-file talk "--main.vtt"))))
+ (emacsconf-set-property-from-slug
+ (plist-get talk :slug)
+ "YOUTUBE_URL"
+ (read-string (format "%s - YouTube URL: " (plist-get talk :scheduled)))))
+
+(defun emacsconf-publish-youtube-step-through-publishing-all ()
+ (interactive)
+ (catch 'done
+ (while t
+ (let ((talk (seq-find (lambda (o)
+ (and (member (plist-get o :status) '("TO_STREAM" "TO_CHECK" "PLAYING" "TO_ARCHIVE"))
+ (not (plist-get o :youtube-url))
+ (plist-get o :video-file)))
+ (emacsconf-publish-prepare-for-display (emacsconf-get-talk-info)))))
+ (unless talk
+ (message "All done so far.")
+ (throw 'done t))
+ (emacsconf-publish-youtube-step-through-publishing-talk talk)))))
+
+(defun emacsconf-publish-toobnix-step-through-publishing-talk (talk)
+ (interactive (list (emacsconf-complete-talk-info
+ (seq-remove
+ (lambda (talk)
+ (or (not (plist-get talk :video-file))
+ (plist-get talk :toobnix-url)))
+ (emacsconf-get-talk-info)))))
+ (kill-new (plist-get talk :video-file))
+ (y-or-n-p
+ (format "Video: %s - create video and upload this filename. Done?"
+ (plist-get talk :video-file)))
+ (kill-new (emacsconf-publish-video-description talk t))
+ (y-or-n-p "Copied description. Paste into description, move first line to title, add to playlist. Done?")
+ (when (emacsconf-talk-file talk "--main.vtt")
+ (kill-new (emacsconf-talk-file talk "--main.vtt"))
+ (y-or-n-p (format "Captions: %s. Add to video elements. Done?"
+ (emacsconf-talk-file talk "--main.vtt"))))
+ (emacsconf-set-property-from-slug
+ (plist-get talk :slug)
+ "TOOBNIX_URL"
+ (read-string (format "%s - Toobnix URL: " (plist-get talk :scheduled)))))
+
+(defun emacsconf-publish-toobnix-step-through-publishing-all ()
+ (interactive)
+ (catch 'done
+ (while t
+ (let ((talk (seq-find (lambda (o)
+ (and (not (plist-get o :toobnix-url))
+ (emacsconf-talk-file o "--main.webm")))
+ (emacsconf-publish-prepare-for-display (emacsconf-get-talk-info)))))
+ (unless talk
+ (message "All done so far.")
+ (throw 'done t))
+ (emacsconf-publish-toobnix-step-through-publishing-talk talk)))))
+
+
;; (emacsconf-publish-video-description (emacsconf-find-talk-info "async") t)
(defun emacsconf-cache-all-video-data ()
- (interactive)
- (mapc
- (lambda (talk)
- (when (plist-get talk :file-prefix)
- (emacsconf-publish-cache-video-data talk)))
- (emacsconf-get-talk-info)))
+ (interactive)
+ (mapc
+ (lambda (talk)
+ (when (plist-get talk :file-prefix)
+ (emacsconf-publish-cache-video-data talk)))
+ (emacsconf-get-talk-info)))
;; (emacsconf-cache-all-video-data t)
(defvar emacsconf-cache-dir (expand-file-name "cache" (file-name-directory emacsconf-org-file)))
@@ -2069,7 +2327,9 @@ This video is available under the terms of the Creative Commons Attribution-Shar
(emacsconf-with-talk-heading talk
(let* ((video-file-name (emacsconf-get-preferred-video (plist-get talk :file-prefix)))
(video-file (and video-file-name (expand-file-name video-file-name emacsconf-cache-dir)))
- (qa-file (emacsconf-talk-file talk "--answers.webm"))
+ (qa-file-name (or (emacsconf-talk-file talk "--answers.webm")
+ (emacsconf-get-preferred-video (concat (plist-get talk :file-prefix) "--answers"))))
+ (qa-file (and qa-file-name (expand-file-name qa-file-name emacsconf-cache-dir)))
(intro-file (expand-file-name (concat (plist-get talk :slug) ".webm")
(expand-file-name "intros" emacsconf-stream-asset-dir)))
duration)
@@ -2204,11 +2464,13 @@ We recommend using a streaming player like mpv to watch the livestreams. Example
(concat "mpv " (plist-get track :stream) "\n"))
emacsconf-tracks
"")
- "</pre><table width=\"100%\"><tr><th>Watch page</th><th>IRC channel (libera.chat)</th><th>URL for streaming player (ex: mpv, vlc, ffplay)</th><th>Low res</th></tr>\n"
+ "</pre><table width=\"100%\"><tr><th>Watch page</th><th>Watch page (low-res)</th><th>IRC channel (libera.chat)</th><th>URL for streaming player (ex: mpv, vlc, ffplay)</th><th>Low res</th></tr>\n"
(mapconcat (lambda (track)
(emacsconf-replace-plist-in-string
- (append (list :year emacsconf-year) track)
- "<tr><td><div class=\"sched-track ${name}\"><a href=\"/${year}/watch/${id}/\">${name}</a></div></td><td><a href=\"${webchat-url}\">${channel}</a></td><td><a href=\"${stream}\">${stream}</a></td><td><a href=\"${480p}\">${id}-480p.webm</a></tr>"))
+ (append (list :year emacsconf-year
+ :watch-base emacsconf-live-base-url
+ ) track)
+ "<tr><td><div class=\"sched-track ${name}\"><a href=\"${watch-base}${year}/watch/${id}/\">${name}</a></div></td><td><a href=\"${watch-base}${year}/watch/${id}-480p/\">${name} (low-res)</a></td><td><a href=\"${webchat-url}\">${channel}</a></td><td><a href=\"${stream}\">${stream}</a></td><td><a href=\"${480p}\">${id}-480p.webm</a></tr>"))
emacsconf-tracks
"\n")
"</table>\n\n"
@@ -2316,6 +2578,7 @@ For better performance, we recommend watching <a href=\"${stream-hires}\">${stre
<li>mpv ${stream-hires}</li>
<li>vlc ${stream-hires}</li>
<li>ffplay ${stream-hires}</li>
+<li>You can also watch it in VLC by choosing menu - Media - Open Network Stream and putting in " (plist-get track :stream) "</li>
</ul>
If you have limited bandwidth, you can watch the low-res stream <a href=\"${480p}\">${480p}</a>.
@@ -2419,6 +2682,8 @@ vlc https://live0.emacsconf.org/gen.webm
ffplay https://live0.emacsconf.org/gen.webm
</pre>
+<p>You can also watch it in VLC by using menu - Media - Open Network Stream and putting in <code>https://live0.emacsconf.org/gen.webm</code></p>.
+
<p>If you experience any disruptions, try reloading the page you're using
to watch the video. If that still doesn't work, please check our
status page at <a href=\"https://status.emacsconf.org\">https://status.emacsconf.org</a> for updates on the
@@ -2535,17 +2800,19 @@ The Q&A room for ${title} has finished. You can find more information about the
There is no live Q&A room for ${title}. You can find more information about the talk at <a href=\"${base-url}${url}\">${base-url}${url}</a>.</body></html>"
)))))))
-(defun emacsconf-publish-media-files-on-change (talk)
+(defun emacsconf-publish-media-files-on-change (talk &optional always-update)
"Publish the files and update the index."
- (interactive (list (emacsconf-complete-talk-info)))
+ (interactive (list (emacsconf-complete-talk-info) current-prefix-arg))
(let ((org-state (if (boundp 'org-state) org-state (plist-get talk :status))))
(if (plist-get talk :public)
;; Copy main files from backstage to public
(let ((public-files (emacsconf-publish-filter-public-files talk)))
(mapc (lambda (file)
- (when (not (file-exists-p (expand-file-name file emacsconf-public-media-directory)))
- (copy-file (expand-file-name file emacsconf-backstage-dir)
- (expand-file-name file emacsconf-public-media-directory) t)))
+ (when (or always-update (not (file-exists-p (expand-file-name file emacsconf-public-media-directory))))
+ (copy-file (if (file-exists-p (expand-file-name file emacsconf-backstage-dir))
+ (expand-file-name file emacsconf-backstage-dir)
+ (expand-file-name file emacsconf-cache-dir))
+ (expand-file-name file emacsconf-public-media-directory) t)))
public-files))
;; Remove files from public
(let ((files (directory-files emacsconf-public-media-directory nil
@@ -2696,6 +2963,9 @@ Tends to be quota-limited, though."
arguments) " "))
(with-current-buffer (get-buffer-create "*YouTube*")
(erase-buffer)
+ (kill-new (concat (car emacsconf-publish-youtube-upload-command)
+ " "
+ (mapconcat #'shell-quote-argument arguments " ")))
(apply #'call-process
(car emacsconf-publish-youtube-upload-command)
nil t t
@@ -2891,5 +3161,130 @@ Tends to be quota-limited, though."
emacsconf-backstage-dir)
t)
(emacsconf-publish-update-talk slug)))
+
+(defun emacsconf-publish-intros-to-backstage ()
+ (interactive)
+ (dolist (talk (emacsconf-get-talk-info))
+ (when (file-exists-p (expand-file-name (concat (plist-get talk :slug) ".webm")
+ (expand-file-name "intros" emacsconf-stream-asset-dir)))
+ (copy-file (expand-file-name (concat (plist-get talk :slug) ".webm")
+ (expand-file-name "intros" emacsconf-stream-asset-dir))
+ (expand-file-name (concat (plist-get talk :file-prefix) "--intro.webm")
+ emacsconf-backstage-dir)
+ t))))
+
+(defun emacsconf-publish-update-transcript ()
+ (interactive)
+ (emacsconf-subed-make-chapter-file-based-on-comments)
+ (let ((talk (emacsconf-resolve-talk (emacsconf-get-slug-from-string (buffer-file-name)))))
+ (emacsconf-publish-media-files-on-change talk)
+ (emacsconf-publish-with-wiki-change
+ (emacsconf-publish-captions-in-wiki talk)
+ (emacsconf-publish-info-pages-for-talk talk))))
+
+;; for emacs.tv
+(defun emacsconf-publish-insert-video-entries (&optional info tags)
+ (interactive)
+ (setq tags (or tags (format ":emacsconf:emacsconf%s:" emacsconf-year)))
+ (dolist (talk (emacsconf-publish-prepare-for-display (or info (emacsconf-get-talk-info))))
+ (when (emacsconf-talk-file talk "--main.webm")
+ (let ((new-entry (emacsconf-replace-plist-in-string
+ (append
+ (list
+ :conf-name emacsconf-name
+ :conf-year emacsconf-year
+ :media-url (format "https://media.emacsconf.org/%s/%s--main.webm"
+ emacsconf-year
+ (plist-get talk :file-prefix))
+ :transcript-url
+ (if (emacsconf-talk-file "--main.vtt"
+ (format "https://media.emacsconf.org/%s/%s--main.vtt"
+ emacsconf-year
+ (plist-get talk :file-prefix)))
+ "")
+ :duration (or (plist-get talk :qa-video-duration)
+ (emacsconf-format-seconds
+ (/ (compile-media-get-file-duration-ms (emacsconf-talk-file talk "--main.webm"))
+ 1000)))
+ :url (concat emacsconf-base-url (plist-get talk :url))
+ :tags (if (plist-get talk :tags) (concat tags (substring (plist-get talk :tags) 1)) tags)
+ :date (format-time-string "%FT%T%z" (plist-get talk :start-time) t))
+ talk
+ (list :youtube-url "" :toobnix-url "" :speakers ""))
+ "* ${title} ${tags}
+:PROPERTIES:
+:DATE: ${date}
+:URL: ${url}
+:DURATION: ${duration}
+:MEDIA_URL: ${media-url}
+:YOUTUBE_URL: ${youtube-url}
+:TOOBNIX_URL: ${toobnix-url}
+:TRANSCRIPT_URL: ${transcript-url}
+:SPEAKERS: ${speakers}
+:SERIES: ${conf-name} ${conf-year}
+:END:
+"
+ )))
+ (with-current-buffer (find-file-noselect emacstv-index-org)
+ (if (and (plist-get talk :youtube-url) (emacstv-find-by-youtube-url (plist-get talk :youtube-url)))
+ (org-entry-put (point) "DATE" (format-time-string "%FT%T%z" (plist-get talk :start-time) t))
+ (goto-char (point-max))
+ (insert new-entry)))))))
+
+(defun emacsconf-publish-insert-video-entries-for-answers (&optional info tags)
+ (interactive)
+ (setq tags (or tags (format ":answers:emacsconf:emacsconf%s:" emacsconf-year)))
+ (dolist (talk (emacsconf-publish-prepare-for-display (or info (emacsconf-get-talk-info))))
+ (when (emacsconf-talk-file talk "--answers.webm")
+ (let ((new-entry (emacsconf-replace-plist-in-string
+ (append
+ (list
+ :conf-name emacsconf-name
+ :conf-year emacsconf-year
+ :youtube-url (plist-get talk :qa-youtube)
+ :toobnix-url (plist-get talk :qa-toobnix)
+ :media-url (format "https://media.emacsconf.org/%s/%s--answers.webm"
+ emacsconf-year
+ (plist-get talk :file-prefix))
+ :transcript-url (if (emacsconf-talk-file talk "--answers.vtt")
+ (format "https://media.emacsconf.org/%s/%s--answers.vtt"
+ emacsconf-year
+ (plist-get talk :file-prefix))
+ "")
+ :url (concat emacsconf-base-url (plist-get talk :url))
+ :duration (or (plist-get talk :qa-video-duration)
+ (emacsconf-format-seconds
+ (/ (compile-media-get-file-duration-ms (emacsconf-talk-file talk "--answers.webm"))
+ 1000)))
+ :tags (if (plist-get talk :tags) (concat tags (substring (plist-get talk :tags) 1)) tags)
+ :date (format-time-string "%FT%T%z" (plist-get talk :start-time) t))
+ talk
+ (list :youtube-url "" :toobnix-url "" :speakers ""))
+ "* Q&A: ${title} ${tags}
+:PROPERTIES:
+:DATE: ${date}
+:URL: ${url}
+:DURATION: ${duration}
+:MEDIA_URL: ${media-url}
+:YOUTUBE_URL: ${youtube-url}
+:TOOBNIX_URL: ${toobnix-url}
+:TRANSCRIPT_URL: ${transcript-url}
+:SPEAKERS: ${speakers}
+:SERIES: ${conf-name} ${conf-year}
+:END:
+"
+ )))
+ (with-current-buffer (find-file-noselect emacstv-index-org)
+ (if (and (plist-get talk :qa-youtube) (emacstv-find-by-youtube-url (plist-get talk :qa-youtube)))
+ (progn
+ (org-entry-put (point) "DATE" (format-time-string "%FT%T%z" (plist-get talk :start-time) t))
+ (org-entry-put (point)
+ "TRANSCRIPT_URL"
+ (if (emacsconf-talk-file talk "--answers.vtt")
+ (format "https://media.emacsconf.org/%s/%s--answers.vtt"
+ emacsconf-year
+ (plist-get talk :file-prefix))
+ "")))
+ (insert new-entry)))))))
;;
(provide 'emacsconf-publish)
diff --git a/emacsconf-schedule.el b/emacsconf-schedule.el
index 9597508..328fabb 100644
--- a/emacsconf-schedule.el
+++ b/emacsconf-schedule.el
@@ -25,20 +25,20 @@
;;; Code:
(defvar emacsconf-schedule-strategies
- '(emacsconf-schedule-allocate-video-time-rounded-to-five)
+ '(emacsconf-schedule-allocate-video-time-round-up-to-five)
"List of scheduling functions.
Each function should take the info and manipulate it as needed, returning the new info.")
-(defvar emacsconf-schedule-max-time 30)
+(defvar emacsconf-schedule-max-time 40)
(defun emacsconf-schedule-allocate-at-most (info)
"Allocate at most `emacsconf-schedule-max-time' to the talks."
(mapcar (lambda (o)
- (when (plist-get o :max-time)
- (plist-put o :time
+ (plist-put o :time
(number-to-string
(min
- (string-to-number (plist-get o :max-time))
- emacsconf-max-time))))
+ (string-to-number (or (plist-get o :time)
+ (plist-get o :max-time)))
+ emacsconf-schedule-max-time)))
o)
info))
@@ -109,6 +109,21 @@ Each function should take the info and manipulate it as needed, returning the ne
emacsconf-schedule-tweak-allocations)))
(emacsconf-schedule-prepare info)))
+(defun emacsconf-schedule-prepare-test-schedule (start &optional info)
+ (interactive (list (org-read-date t t nil "Start time: ")))
+ (let* ((emacsconf-schedule-break-time 2)
+ (emacsconf-schedule-lunch-time 2)
+ (emacsconf-schedule-max-time 1)
+ (emacsconf-schedule-default-buffer-minutes 1)
+ (emacsconf-schedule-default-buffer-minutes-for-live-q-and-a 1)
+ (emacsconf-schedule-strategies '(emacsconf-schedule-allocate-buffer-time
+ emacsconf-schedule-override-breaks
+ emacsconf-schedule-allocate-buffer-time-at-most-max-time
+ emacsconf-schedule-allocate-max-time
+ emacsconf-schedule-allocate-at-most
+ emacsconf-schedule-tweak-allocations)))
+ (emacsconf-schedule-prepare info)))
+
(defun emacsconf-schedule-copy-previous-track (info)
"Use :set-track to update INFO."
(cl-loop with track = (plist-get (car info) :set-track)
@@ -124,7 +139,8 @@ Each function should take the info and manipulate it as needed, returning the ne
(plist-put o :buffer
(number-to-string
(if (string-match "live" (or (plist-get o :q-and-a) ""))
- (min (string-to-number (plist-get o :max-time))
+ (min (string-to-number (or (plist-get o :max-time)
+ (plist-get o :time)))
emacsconf-schedule-default-buffer-minutes-for-live-q-and-a)
emacsconf-schedule-default-buffer-minutes))))
o)
@@ -258,8 +274,9 @@ Pairs with `emacsconf-schedule-dump-sexp'."
(list "Status" "Slug" "Schedule" "Time" "Buffer" "Title" "Name" "Q&A" "Availability"))
(mapcar #'emacsconf-schedule-format-summary-row (or info (emacsconf-get-talk-info)))))
-(defun emacsconf-schedule-update-from-info (info)
- (interactive (list (or emacsconf-schedule-draft (emacsconf-get-talk-info))))
+(defun emacsconf-schedule-update-from-info (&optional info)
+ (interactive)
+ (setq info (or info emacsconf-schedule-draft (emacsconf-get-talk-info)))
(save-window-excursion
(save-excursion
(mapc (lambda (talk)
@@ -267,42 +284,41 @@ Pairs with `emacsconf-schedule-dump-sexp'."
(org-entry-put (point) "SCHEDULED" (plist-get talk :scheduled))
(org-entry-put (point) "TRACK" (plist-get talk :track))
(org-entry-put (point) "TIME" (plist-get talk :time)))
- (emacsconf-filter-talks info)))))
+ (emacsconf-filter-talks info))
+ (setq emacsconf-schedule-draft nil))))
-(defun emacsconf-schedule-save-emailed-times (info &optional field force)
- (interactive (list (or emacsconf-schedule-draft (emacsconf-get-talk-info))
- (read-string "Field: ") current-prefix-arg))
+(defun emacsconf-schedule-save-emailed-times (info &optional field)
+ (interactive (list (or emacsconf-schedule-draft (emacsconf-get-talk-info))))
+ (setq field (or field "EMAILED_SCHEDULE"))
(save-window-excursion
(save-excursion
(mapc (lambda (talk)
(emacsconf-go-to-talk (plist-get talk :slug))
- (when (and (plist-get talk :scheduled)
- (or force (null (org-entry-get (point)
- (or field "ORIGINAL_SCHEDULE")))))
+ (when (plist-get talk :scheduled)
(org-entry-put (point)
- (or field "ORIGINAL_SCHEDULE")
+ field
(replace-regexp-in-string "[<>]" "" (plist-get talk :scheduled)))))
(emacsconf-filter-talks info)))))
(defvar emacsconf-schedule-svg-modify-functions '(emacsconf-schedule-svg-color-by-track) "Functions to run to modify the display of each item.")
(defvar emacsconf-use-absolute-url nil "Non-nil means try to use absolute URLs.")
-(defun emacsconf-schedule-svg-track (svg base-x base-y width height start-time end-time info)
+(defun emacsconf-schedule-svg-track (svg base-x base-y width height start-time end-time info &optional direction)
"Draw the actual rectangles and text for the talks."
- (let ((scale (/ width (float-time (time-subtract end-time start-time)))))
+ (let ((scale (/ (if (eq direction 'vertical) height width) (float-time (time-subtract end-time start-time)))))
(mapc
(lambda (o)
(let* ((offset (floor (* scale (float-time (time-subtract (plist-get o :start-time) start-time)))))
(size (floor (* scale (float-time (time-subtract (plist-get o :end-time) (plist-get o :start-time))))))
- (x (+ base-x offset))
- (y base-y)
+ (x (if (eq direction 'vertical) base-x (+ base-x offset)))
+ (y (if (eq direction 'vertical) (+ base-y offset) base-y))
(node (dom-node
'rect
(list
(cons 'x x)
(cons 'y y)
(cons 'opacity "0.8")
- (cons 'width size)
- (cons 'height (1- height))
+ (cons 'width (if (eq direction 'vertical) (1- width) size))
+ (cons 'height (if (eq direction 'vertical) size (1- height)))
(cons 'stroke "black")
(cons 'stroke-dasharray
(if (string-match "live" (or (plist-get o :q-and-a) "live"))
@@ -335,7 +351,8 @@ Pairs with `emacsconf-schedule-dump-sexp'."
(dom-node
'g
`((transform . ,(format "translate(%d,%d)"
- (+ x size -2) (+ y height -2))))
+ (if (eq direction 'vertical) x (+ x size -2))
+ (if (eq direction 'vertical) (+ y size -2) (+ y height -2)))))
(dom-node
'text
(list
@@ -343,7 +360,7 @@ Pairs with `emacsconf-schedule-dump-sexp'."
(cons 'x 0)
(cons 'y 0)
(cons 'font-size 10)
- (cons 'transform "rotate(-90)"))
+ (cons 'transform (if (eq direction 'vertical) nil "rotate(-90)")))
(svg--encode-text (or (plist-get o :slug) (plist-get o :title))))))))
(run-hook-with-args
'emacsconf-schedule-svg-modify-functions
@@ -353,44 +370,73 @@ Pairs with `emacsconf-schedule-dump-sexp'."
parent)))
info)))
-(defun emacsconf-schedule-svg-day (elem label width height start end tracks)
+(defun emacsconf-schedule-svg-day (elem label width height start end tracks &optional direction)
"Add the time scale and the talks on a given day."
- (let* ((label-margin 15)
- (track-height (/ (- height (* 2 label-margin)) (length tracks)))
- (x 0) (y label-margin)
- (scale (/ width (float-time (time-subtract end start))))
+ (let* ((label-margin (if (eq direction 'vertical) 40 15))
+ (x (if (eq direction 'vertical) label-margin 0))
+ (y (if (eq direction 'vertical) label-margin label-margin))
+ (track-size (if (eq direction 'vertical)
+ (/ (- width (* 2 label-margin)) (length tracks))
+ (/ (- height (* 2 label-margin)) (length tracks))))
+ (scale (/ (if (eq direction 'vertical) height width) (float-time (time-subtract end start))))
(time start))
(dom-append-child elem (dom-node 'title nil (concat "Schedule for " label)))
(svg-rectangle elem 0 0 width height :fill "white")
- (svg-text elem label :x 3 :y (- label-margin 3) :fill "black" :font-size "10")
+ (if (eq direction 'vertical)
+ (svg-text elem label :x 3 :y (- label-margin 10) :fill "black" :font-size "10")
+ (svg-text elem label :x 3 :y (- label-margin 3) :fill "black" :font-size "10"))
(mapc (lambda (track)
(emacsconf-schedule-svg-track
- elem x y width track-height
- start end track)
- (setq y (+ y track-height)))
+ elem x y
+ (if (eq direction 'vertical) track-size width)
+ (if (eq direction 'vertical) height track-size)
+ start end track
+ direction)
+ (if (eq direction 'vertical)
+ (setq x (+ x track-size))
+ (setq y (+ y track-size))))
tracks)
;; draw grid
(while (time-less-p time end)
- (let ((x (* (float-time (time-subtract time start)) scale)))
+ (let ((x (if (eq direction 'vertical)
+ 3
+ (* (float-time (time-subtract time start)) scale)))
+ (y (if (eq direction 'vertical)
+ (+ (* (float-time (time-subtract time start)) scale) label-margin)
+ 3)))
(dom-append-child
elem
(dom-node
'g
- `((transform . ,(format "translate(%d,%d)" x label-margin)))
- (dom-node
- 'line
- `((stroke . "darkgray")
- (x1 . 0)
- (y1 . 0)
- (x2 . 0)
- (y2 . ,(- height label-margin label-margin))))
+ `((transform . ,(format "translate(%d,%d)" x y)))
+ (if (eq direction 'vertical)
+ (dom-node
+ 'line
+ `((stroke . "darkgray")
+ (x1 . ,label-margin)
+ (y1 . 0)
+ (x2 . ,(- width label-margin))
+ (y2 . 0)))
+ (dom-node
+ 'line
+ `((stroke . "darkgray")
+ (x1 . 0)
+ (y1 . 0)
+ (x2 . 0)
+ (y2 . ,(- height label-margin label-margin)))))
(dom-node
'text
- `((fill . "black")
- (x . 0)
- (y . ,(- height 2 label-margin))
- (font-size . 10)
- (text-anchor . "left"))
+ (if (eq direction 'vertical)
+ `((fill . "black")
+ (x . 0)
+ (y . 0)
+ (font-size . 10)
+ (dy . ".4em"))
+ `((fill . "black")
+ (x . 0)
+ (y . ,(- height label-margin -5))
+ (font-size . 10)
+ (text-anchor . "left")))
(svg--encode-text (format-time-string "%-l %p" time emacsconf-timezone)))))
(setq time (time-add time (seconds-to-time 3600)))))
elem))
@@ -410,7 +456,7 @@ Pairs with `emacsconf-schedule-dump-sexp'."
"peachpuff")
(t "gray"))))
-(defun emacsconf-schedule-svg (width height &optional info)
+(defun emacsconf-schedule-svg (width height &optional info direction)
"Make the schedule SVG for INFO."
(setq info (or info (emacsconf-publish-prepare-for-display (emacsconf-get-talk-info))))
(let ((days (seq-group-by (lambda (o)
@@ -429,15 +475,16 @@ Pairs with `emacsconf-schedule-dump-sexp'."
:start start
:end end
:tracks (emacsconf-by-track (cdr o)))))
- days))))
+ days)
+ direction)))
(defun emacsconf-schedule-svg-color-by-status (o node &optional _)
"Set talk color based on status.
Processing: palegoldenrod,
Waiting to be assigned a captioner: yellow,
-Captioning in progress: lightgreen,
-To check: green,
-Ready to stream: blue,
+Captioning in progress: lightblue,
+To check: lightgreen,
+Ready to stream: green,
Other status: gray"
(unless (plist-get o :invalid)
(dom-set-attribute node 'fill
@@ -449,32 +496,37 @@ Other status: gray"
("TO_ASSIGN"
"yellow")
("TO_CAPTION"
- "lightgreen")
+ "lightblue")
("TO_CHECK"
- "green")
+ "#90ee90")
("TO_STREAM"
- "lightblue")
+ "green")
("TODO"
"lightgray")
(_ "gray")))))
-(defun emacsconf-schedule-svg-days (width height days)
+(defun emacsconf-schedule-svg-days (width height days &optional direction)
"Display multiple days."
(let ((svg (svg-create width height))
- (day-height (/ height (length days)))
- (y 0))
+ (day-height (if (eq direction 'vertical) height (/ height (length days))))
+ (day-width (if (eq direction 'vertical) (/ width (length days)) width))
+ (y 0)
+ (x 0))
(dom-append-child svg (dom-node 'title nil "Graphical view of the schedule"))
(mapc
(lambda (day)
- (let ((group (dom-node 'g `((transform . ,(format "translate(0,%d)" y))))))
+ (let ((group (dom-node 'g `((transform . ,(format "translate(%d,%d)" x y))))))
(dom-append-child svg group)
(emacsconf-schedule-svg-day group
(plist-get day :label)
- width day-height
+ day-width day-height
(date-to-time (plist-get day :start))
(date-to-time (plist-get day :end))
- (plist-get day :tracks)))
- (setq y (+ y day-height)))
+ (plist-get day :tracks)
+ direction))
+ (if (eq direction 'vertical)
+ (setq x (+ x day-width))
+ (setq y (+ y day-height))))
days)
svg))
@@ -662,33 +714,49 @@ Talks with a FIXED_TIME property are not moved."
(message "%s" (string-join results "\n"))
results)))
-(defun emacsconf-schedule-check-time (label o &optional from-time to-time day)
+(defun emacsconf-schedule-check-time (label o &rest args)
"FROM-TIME and TO-TIME should be nil strings like HH:MM in EST.
-DAY should be YYYY-MM-DD if specified.
+DAY should be YYYY-MM-DD or Sat/Sun if specified.
Both start and end time are tested."
(let* ((start-time (format-time-string "%H:%M" (plist-get o :start-time)))
(end-time (format-time-string "%H:%M" (plist-get o :end-time)))
- result)
- (setq result
- (or
- (and (null o) (format "%s: Not found" label))
- (and from-time (string< start-time from-time)
- (format "%s: Starts at %s before %s" label start-time from-time))
- (and to-time (string< to-time end-time)
- (format "%s: Ends at %s after %s" label end-time to-time))
- (and day
- (not (string= (format-time-string "%Y-%m-%d" (plist-get o :start-time))
- day))
- (format "%s: On %s instead of %s"
- label
- (format-time-string "%Y-%m-%d" (plist-get o :start-time))
- day))))
- (when result (plist-put o :invalid result))
- result))
+ (date (format-time-string "%Y-%m-%d" (plist-get o :start-time)))
+ (day (format-time-string "%a" (plist-get o :start-time)))
+ (result t) error)
+ (if (null o)
+ (setq error (format "%s: Not found" label))
+ (while args
+ (pcase (car args)
+ ('or ; skip the rest
+ (if error
+ (setq args (cdr args)
+ error nil)
+ (setq args nil)))
+ ('and ; skip the rest if nil
+ (setq args (if error nil (cdr args))))
+ (_
+ (let ((from-time (pop args))
+ (to-time (pop args))
+ (limit-day (pop args)))
+ (cond
+ ((and from-time (string< start-time from-time))
+ (setq error (format "%s: Starts at %s before %s" label start-time from-time)))
+ ((and to-time (string< to-time end-time))
+ (setq error (format "%s: Ends at %s after %s" label end-time to-time)))
+ ((and limit-day (string-match "Sat\\|Sun" limit-day))
+ (when (not (string= day limit-day))
+ (setq error (format "%s: On %s instead of %s"
+ label day limit-day))))
+ (limit-day
+ (when (not (string= date limit-day))
+ (setq error (format "%s: On %s instead of %s"
+ label date limit-day))))))))))
+ (when error (plist-put o :invalid error))
+ error))
(defun emacsconf-schedule-q-and-a-p (talk)
"Return non-nil if TALK has a Q&A scheduled for the event."
- (not (string-match "after the event" (or (plist-get talk :q-and-a) ""))))
+ (not (string-match "none\\|email\\|after the event" (or (plist-get talk :qa-type) ""))))
(defun emacsconf-schedule-get-time-constraint (o)
(when (emacsconf-schedule-q-and-a-p o)
@@ -696,16 +764,30 @@ Both start and end time are tested."
hours
start
(pos 0)
- (result (list nil nil nil)))
- (while (string-match "\\([<>]\\)=? *\\([0-9]+:[0-9]+\\) *EST" avail pos)
- (setf (elt result (if (string= (match-string 1 avail) ">")
- 0
- 1))
- (match-string 2 avail))
- (setq pos (match-end 0)))
- (when (string-match "[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]" avail)
- (setf (elt result 2) (match-string 0 avail)))
- result)))
+ result)
+ (with-temp-buffer
+ (insert avail)
+ (goto-char (point-min))
+ (while (not (eobp))
+ (cond
+ ((looking-at "\\([<>]\\)=? *\\([0-9]+:[0-9]+\\) *EST\\( [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]\\|Sat\\|Sun\\)?")
+ (push (and (string= (match-string 1) ">") ; start time
+ (match-string 2))
+ result)
+ (push (and (string= (match-string 1) "<") ; end time
+ (match-string 2))
+ result)
+ (push (and (match-string 3) (string-trim (match-string 3))) result)
+ (goto-char (match-end 0)))
+ ((looking-at " or ")
+ (push 'or result)
+ (goto-char (match-end 0)))
+ ((looking-at " and ")
+ (push 'and result)
+ (goto-char (match-end 0)))
+ (t (goto-char (point-max))))))
+ (reverse result))))
+;; (emacsconf-schedule-get-time-constraint '(:q_and_a "live" :availability ">= 12:00 EST Sat or <= 12:00 EST Sun - more info"))
(defun emacsconf-schedule-rename-etc-timezone (s)
"Change Etc/GMT-3 etc. to UTC+3 etc., since Etc uses negative signs and this is confusing."
@@ -713,52 +795,62 @@ Both start and end time are tested."
((string-match "Etc/GMT\\+\\(.*\\)" s) (concat "UTC-" (match-string 1 s)))
(t s)))
+;; (emacsconf-schedule-format-time-constraint (emacsconf-schedule-get-time-constraint '(:q_and_a "live" :availability ">= 12:00 EST Sat or <= 12:00 EST Sun - more info")) t "America/Vancouver")
(defun emacsconf-schedule-format-time-constraint (constraints &optional include-offset local-timezone)
"Format CONSTRAINTS for display."
;; actually a talk object, extract constraints from it instead
- (when (not (= (length constraints) 3))
+ (when (plist-get constraints :title)
(setq constraints (emacsconf-schedule-get-time-constraint constraints)))
- (string-join
- (delq nil
- (list
- (let ((start-time (car constraints))
- (end-time (cadr constraints))
- (start-local (and (car constraints)
- local-timezone
- (format-time-string
- "%H:%M"
- (date-to-time (concat emacsconf-date " " (car constraints) ":00 " emacsconf-timezone-offset))
- local-timezone)))
- (end-local (and (cadr constraints)
- local-timezone
- (format-time-string
- "%H:%M"
- (date-to-time (concat emacsconf-date " " (cadr constraints) ":00 " emacsconf-timezone-offset))
- local-timezone))))
- (cond
- ((and start-time end-time)
- (concat
- (format "between %s-%s" start-time end-time)
- (emacsconf-surround " " (and include-offset emacsconf-timezone-offset) "" "")
- (if local-timezone
- (format " (%s-%s %s)" start-local end-local (emacsconf-schedule-rename-etc-timezone local-timezone))
- "")))
- (start-time
- (concat
- (format ">= %s" start-time)
- (emacsconf-surround " " (and include-offset emacsconf-timezone-offset) "" "")
- (if local-timezone
- (format " (%s %s)" start-local (emacsconf-schedule-rename-etc-timezone local-timezone))
- "")))
- (end-time
- (concat
- (format "<= %s" end-time)
- (emacsconf-surround " " (and include-offset emacsconf-timezone-offset) "" "")
- (if local-timezone
- (format " (%s %s)" end-local (emacsconf-schedule-rename-etc-timezone local-timezone))
- "")))))
- (if (elt constraints 2) (format "on %s" (elt constraints 2)))))
- " and "))
+ (let (results)
+ (while constraints
+ (push (pcase (car constraints)
+ ('or (pop constraints)
+ "or")
+ ('and (pop constraints)
+ "and")
+ (_
+ (let* ((from-time (pop constraints))
+ (to-time (pop constraints))
+ (from-local (and from-time local-timezone
+ (format-time-string
+ "%H:%M"
+ (date-to-time (concat emacsconf-date " " from-time ":00 " emacsconf-timezone-offset))
+ local-timezone)))
+ (to-local (and to-time local-timezone
+ (format-time-string
+ "%H:%M"
+ (date-to-time (concat emacsconf-date " " to-time ":00 "
+ emacsconf-timezone-offset))
+ local-timezone)))
+ (limit-day (pop constraints)))
+ (string-trim
+ (concat
+ (cond
+ ((and from-time to-time)
+ (concat
+ (format "between %s-%s" from-time to-time)
+ (emacsconf-surround " " (and include-offset emacsconf-timezone-offset) "" "")
+ (if local-timezone
+ (format "(%s-%s %s) " from-local to-local (emacsconf-schedule-rename-etc-timezone local-timezone))
+ "")))
+ (from-time
+ (concat
+ (format ">= %s" from-time)
+ (emacsconf-surround " " (and include-offset emacsconf-timezone-offset) "" "")
+ (if local-timezone
+ (format " (%s %s)" from-local (emacsconf-schedule-rename-etc-timezone local-timezone))
+ "")))
+ (to-time
+ (concat
+ (format "<= %s " to-time)
+ (emacsconf-surround "" (and include-offset emacsconf-timezone-offset) "" "")
+ (if local-timezone
+ (format " (%s %s)" to-local (emacsconf-schedule-rename-etc-timezone local-timezone))
+ "")))
+ (t ""))
+ (if limit-day (concat " " limit-day "")))))))
+ results))
+ (string-join (reverse results) " ")))
(defun emacsconf-schedule-validate-all-talks-present (sched &optional list)
(let* ((sched-slugs (mapcar (lambda (o) (plist-get o :slug))
@@ -777,6 +869,12 @@ Both start and end time are tested."
(when diff
(list (concat "Missing talks: " (string-join diff ", "))))))
+(defun emacsconf-schedule-validate-no-cancelled-talks (sched &optional list)
+ (let ((cancelled (seq-keep (lambda (o) (when (string= (plist-get o :status) "CANCELLED") (plist-get o :slug)))
+ sched)))
+ (when cancelled
+ (list (concat "Cancelled talks: " (string-join cancelled ", "))))))
+
(defun emacsconf-schedule-validate-no-duplicates (sched &optional info)
(let* ((sched-slugs (mapcar (lambda (o) (plist-get o :slug))
(emacsconf-filter-talks sched)))
@@ -785,6 +883,18 @@ Both start and end time are tested."
(when dupes
(list (concat "Duplicate talks: " (mapconcat 'car dupes ", "))))))
+(defun emacsconf-schedule-validate-videos-fit-in-time (sched &optional info)
+ "If there are prerecorded videos, make sure they fit in the time allocated."
+ (seq-keep
+ (lambda (o)
+ (when (and (plist-get o :video-time)
+ (> (string-to-number (plist-get o :video-time))
+ (string-to-number (plist-get o :time))))
+ (format "%s: video: %s, allocated %s"
+ (plist-get o :slug)
+ (plist-get o :video-time)
+ (plist-get o :time))))
+ sched))
(defvar emacsconf-schedule-validation-functions '(emacsconf-schedule-validate-time-constraints
emacsconf-schedule-validate-live-q-and-a-sessions-are-staggered
emacsconf-schedule-validate-all-talks-present
diff --git a/emacsconf-stream.el b/emacsconf-stream.el
index 211749b..d526367 100644
--- a/emacsconf-stream.el
+++ b/emacsconf-stream.el
@@ -24,9 +24,9 @@
;;; Code:
-(defvar emacsconf-stream-dir "/data/emacsconf/stream/"
+(defvar emacsconf-stream-dir (format "/data/emacsconf/shared/%s/assets/stream/" emacsconf-year)
"Directory where the stream versions are.
-Files should be in YEAR/file-prefix--main.webm and file-prefix--main.vtt.")
+Files should be file-prefix--main.webm and file-prefix--main.vtt.")
(defvar emacsconf-stream-host "res.emacsconf.org")
(defun emacsconf-stream-track-login (track)
@@ -72,7 +72,7 @@ If the element doesn't have a tspan child, use the element itself."
(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))
+ ;; (setq text (svg--encode-text text))
(let ((node (or (dom-child-by-tag
(car (dom-by-id dom id))
'tspan)
@@ -261,11 +261,10 @@ especially when two things need to happen close together."
(defun emacsconf-stream-get-filename (talk)
"Return the local filename for the video file for TALK.
-Final files should be stored in /data/emacsconf/stream/YEAR/file-prefix--main.webm."
+Final files should be stored in emacsconf-stream-dir/file-prefix--main.webm."
(expand-file-name
(concat (plist-get talk :file-prefix) "--main.webm")
- (expand-file-name emacsconf-year
- emacsconf-stream-dir)))
+ emacsconf-stream-dir))
(defun emacsconf-stream-play-video (talk)
"Play just the video for TALK."
@@ -414,10 +413,11 @@ With a prefix argument (\\[universal-argument]), clear the overlay."
i
(substring "123456789 123456789 123456789 123456789 123456789 123456789 "
(1+ (length (format "%s %02d" (plist-get talk :slug) i))))))))
- (copy-file
- (expand-file-name "template.webm" dir)
- (expand-file-name (concat (plist-get talk :file-prefix) "--main.webm") dir)
- t))))
+ (unless (file-exists-p (expand-file-name (concat (plist-get talk :file-prefix) "--main.webm") dir))
+ (copy-file
+ (expand-file-name "template.webm" dir)
+ (expand-file-name (concat (plist-get talk :file-prefix) "--main.webm") dir)
+ t)))))
(defun emacsconf-stream-display-talk-info (talk)
(interactive (list (emacsconf-complete-talk-info)))
@@ -607,7 +607,7 @@ With a prefix argument (\\[universal-argument]), clear the overlay."
(shell-quote-argument (expand-file-name (concat (plist-get talk :slug) ".svg")
dir)))))
(setq prev talk))
- (emacsconf-filter-talks (cdr track)))))
+ (emacsconf-filter-talks (cdr track)))))
by-track)))
@@ -818,6 +818,7 @@ ffplay URL
(defvar emacsconf-stream-track "General")
(defvar emacsconf-stream-clock-buffer "*emacsconf*")
(defvar emacsconf-stream-clock-timer nil)
+(defvar emacsconf-stream-random-timer nil)
(require 'diary-lib)
(require 'text-property-search)
@@ -861,10 +862,55 @@ ffplay URL
" to go" (if message ": " ""))
""))))
"")
- (or message ""))
+ (or message "")
+ "\n\n"
+ (propertize "Enjoy EmacsConf!" 'emacsconf-random t))
(when (timerp emacsconf-stream-clock-timer) (cancel-timer emacsconf-stream-clock-timer))
(emacsconf-stream-update-time)
- (setq emacsconf-stream-clock-timer (run-at-time t 1 #'emacsconf-stream-update-time))))
+ (setq emacsconf-stream-clock-timer (run-at-time t 1 #'emacsconf-stream-update-time))
+ (setq emacsconf-stream-clock-timer (run-at-time t 10 #'emacsconf-stream-update-random))))
+
+(defvar emacsconf-stream-random-file (expand-file-name "fortune.txt" emacsconf-cache-dir))
+(defvar emacsconf-stream-random-data nil)
+
+(defun emacsconf-stream-shuffle-list (list)
+ "Shuffle LIST using Fisher-Yates algorithm."
+ (let ((shuffled (copy-sequence list)))
+ (dotimes (i (1- (length shuffled)))
+ (let* ((j (+ i (random (- (length shuffled) i))))
+ (temp (nth i shuffled)))
+ (setf (nth i shuffled) (nth j shuffled))
+ (setf (nth j shuffled) temp)))
+ shuffled))
+
+(defun emacsconf-stream-get-random-string ()
+ (when (and (not emacsconf-stream-random-data)
+ emacsconf-stream-random-file (file-exists-p emacsconf-stream-random-file))
+ (setq emacsconf-stream-random-data
+ (emacsconf-stream-shuffle-list
+ (with-temp-buffer
+ (insert-file-contents emacsconf-stream-random-file)
+ (split-string (string-trim (buffer-string)) "\n%\n")))))
+ (when emacsconf-stream-random-data
+ (pop emacsconf-stream-random-data)))
+
+(defun emacsconf-stream-update-random ()
+ (if (get-buffer emacsconf-stream-clock-buffer)
+ (when (get-buffer-window emacsconf-stream-clock-buffer)
+ (with-current-buffer emacsconf-stream-clock-buffer
+ (save-excursion
+ (goto-char (point-min))
+ (let (match)
+ (while (setq match (text-property-search-forward 'emacsconf-random))
+ (goto-char (prop-match-beginning match))
+ (add-text-properties
+ (prop-match-beginning match)
+ (prop-match-end match)
+ (list 'display
+ (emacsconf-stream-get-random-string)))
+ (goto-char (prop-match-end match)))))))
+ (when (timerp emacsconf-stream-random-timer)
+ (cancel-timer emacsconf-stream-random-timer))))
(defun emacsconf-stream-update-time ()
"Update the displayed time."
@@ -1164,10 +1210,13 @@ XDG_RUNTIME_DIR=\"/run/user/%d\"
(defun emacsconf-stream-crontabs (&optional test-mode info)
"Write the streaming users' crontab files.
If TEST-MODE is non-nil, use the videos in the test directory.
+If TEST-MODE is a date, use that as the starting date.
If INFO is non-nil, use that as the schedule instead."
- (interactive)
+ (interactive (list (when current-prefix-arg (org-read-date t t nil "Start time: "))))
(let ((emacsconf-publishing-phase 'conference))
- (setq info (or info (emacsconf-publish-prepare-for-display (emacsconf-get-talk-info))))
+ (setq info (or info (emacsconf-publish-prepare-for-display (emacsconf-get-talk-info))))
+ (when (and test-mode (listp test-mode))
+ (setq info (emacsconf-schedule-prepare-test-schedule test-mode info)))
(dolist (track emacsconf-tracks)
(let ((talks (seq-filter (lambda (talk)
(string= (plist-get talk :track)
@@ -1193,5 +1242,117 @@ If INFO is non-nil, use that as the schedule instead."
(plist-put track :autopilot nil)
(emacsconf-stream-track-ssh track "crontab -r")))
+(defun emacsconf-stream-copy-livestream-description (shift)
+ (interactive (list (completing-read "Shift: " (mapcar (lambda (o) (plist-get o :id)) emacsconf-shifts))))
+ (when (stringp shift) (setq shift (seq-find (lambda (o) (string= (plist-get o :id) shift)) emacsconf-shifts)))
+ (let* ((track-id (when (string-match "-\\([a-z]+?\\)$" (plist-get shift :id)) (match-string 1 (plist-get shift :id))))
+ (desc (emacsconf-replace-plist-in-string
+ (list
+ :track-name (plist-get shift :track)
+ :start (format-time-string "%Y-%m-%d %-l:%M %p %Z (UTC %z)" (date-to-time (plist-get shift :start)))
+ :end (format-time-string "%Y-%m-%d %-l:%M %p %Z (UTC %z)" (date-to-time (plist-get shift :end)))
+ :start-day (format-time-string "%b %-e %a %p" (date-to-time (plist-get shift :start)))
+ :year emacsconf-year
+ :track-id track-id
+ :irc-channels (concat
+ (string-join
+ (seq-keep (lambda (track)
+ (unless (string= (plist-get track :id) track-id)
+ (plist-get track :channel)))
+ emacsconf-tracks)
+ ",")
+ ","
+ (plist-get (emacsconf-get-track track-id) :channel)))
+ "
+${track-name} - ${start-day} EmacsConf ${year}
+
+This for the ${track-name} track of EmacsConf, the conference about the joy of Emacs and Emacs Lisp.
+Start: ${start}
+End: ${end}
+
+Watch using free/open source software: https://live.emacsconf.org/${year}/watch/${track-id}/
+Conference info: https://emacsconf.org/${year}/
+Schedule: https://emacsconf.org/${year}/talks/
+Chat on #emacsconf-${track-id} via https://chat.emacsconf.org/?join=emacsconf,emacsconf-org,emacsconf-accessible,${irc-channels} or irc.libera.chat using your favorite IRC client
+Etherpad: Use the Etherpad links from the talk page; general comments in https://pad.emacsconf.org/${year}
+
+Videos are shared under the terms of the Creative Commons Attribution-ShareAlike 4.0
+International (CC BY-SA 4.0) license. Please observe the guidelines for conduct: https://emacsconf.org/conduct/
+")))
+ (when (called-interactively-p 'any)
+ (kill-new desc))
+ desc))
+
+(defun emacsconf-stream-toobnix-copy-livestream-description (track)
+ (interactive (list (emacsconf-complete-track)))
+ (when (stringp track) (setq track (emacsconf-get-track track)))
+ (let* ((track-id (plist-get track :id))
+ (desc (emacsconf-replace-plist-in-string
+ (list
+ :track-name (plist-get track :name)
+ :dates emacsconf-dates
+ :conf-name emacsconf-name
+ :year emacsconf-year
+ :track-id track-id
+ :base-url emacsconf-base-url
+ :channel (plist-get track :channel)
+ :chat-url (plist-get track :webchat-url)
+ :irc-channels (concat
+ (string-join
+ (seq-keep (lambda (track)
+ (unless (string= (plist-get track :id) track-id)
+ (plist-get track :channel)))
+ emacsconf-tracks)
+ ",")
+ ","
+ (plist-get track :channel)))
+ "
+${track-name} - ${conf-name} ${year}
+
+This for the ${track-name} track of ${conf-name} (${dates})
+
+Watch using free/open source software: https://live.emacsconf.org/${year}/watch/${track-id}/
+Conference info: ${base-url}${year}/
+Schedule: ${base-url}${year}/talks/
+Chat on #${channel} via ${chat-url} or irc.libera.chat using your favorite IRC client
+Etherpad: Use the Etherpad links from the talk page; general comments in https://pad.emacsconf.org/${year}
+
+Videos are shared under the terms of the Creative Commons Attribution-ShareAlike 4.0
+International (CC BY-SA 4.0) license. Please observe the guidelines for conduct: https://emacsconf.org/conduct/
+")))
+ (when (called-interactively-p 'any)
+ (kill-new desc))
+ desc))
+
+(defun emacsconf-stream-populate-random-package-file ()
+ (interactive)
+ (with-temp-file (expand-file-name "fortune.txt" emacsconf-cache-dir)
+ (dolist (entry
+ (emacsconf-stream-shuffle-list
+ (seq-mapcat (lambda (f)
+ (let ((base (file-name-base f)))
+ (mapcar
+ (lambda (entry)
+ (list base
+ (symbol-name (car entry))
+ (elt (cdr entry) 2)))
+ (cdr
+ (with-temp-buffer
+ (insert-file-contents
+ (expand-file-name "archive-contents" f))
+ (goto-char (point-min))
+ (read (current-buffer)))))))
+ (directory-files
+ (expand-file-name "archives" package-user-dir) t
+ directory-files-no-dot-files-regexp))))
+ (unless (bobp) (insert "\n%\n"))
+ (insert
+ (format "%s: %s (%s)"
+ (elt entry 1)
+ (elt entry 2)
+ (pcase (elt entry 0)
+ ("gnu" "GNU ELPA")
+ ("nongnu" "NonGNU ELPA")
+ ("melpa" "MELPA")))))))
(provide 'emacsconf-stream)
;;; emacsconf-stream.el ends here
diff --git a/emacsconf-subed.el b/emacsconf-subed.el
index 37daa1a..82a07ad 100644
--- a/emacsconf-subed.el
+++ b/emacsconf-subed.el
@@ -147,13 +147,11 @@ TYPE can be 'end if you want the match end instead of the beginning."
(let ((new-filename (concat (file-name-sans-extension (buffer-file-name)) "--chapters.vtt"))
(subtitles (subed-subtitle-list))
(subed-auto-play-media nil))
- (when (or (not (file-exists-p new-filename))
- (yes-or-no-p (format "%s exists. Overwrite? " new-filename)))
- (subed-create-file
- new-filename
- (emacsconf-subed-list-chapter-markers-based-on-comments
- subtitles)
- t))))
+ (subed-create-file
+ new-filename
+ (emacsconf-subed-list-chapter-markers-based-on-comments
+ subtitles)
+ t)))
(defun emacsconf-subed-list-chapter-markers-based-on-comments (subtitles)
"Make a list of subtitles based on which SUBTITLES have comments."
@@ -299,14 +297,20 @@ Create it if necessary."
(interactive "e")
(goto-char (posn-point (event-start event)))
(skip-syntax-backward "w")
- (subed-split-subtitle)
+ (if (derived-mode-p 'subed-mode)
+ (subed-split-subtitle)
+ (insert "\n"))
(recenter))
(defun emacsconf-subed-merge-and-unfill ()
"Merge this subtitle with the next one."
(interactive)
- (subed-merge-with-next)
- (emacsconf-subed-unfill-paragraph))
+ (if (derived-mode-p 'subed-mode)
+ (progn
+ (subed-merge-with-next)
+ (emacsconf-subed-unfill-paragraph))
+ (goto-char (line-end-position))
+ (join-line)))
(defun emacsconf-subed-unfill-paragraph ()
"Sometimes the regular fill doesn't work."
@@ -357,12 +361,12 @@ Create it if necessary."
nil
(* i 5000)
(1- (* i 5000))
- (format "#+OUTPUT: %s.webm\n[[file:%s]]\n%s"
- (plist-get talk :slug)
+ (emacsconf-pad-expand-intro talk)
+ (format "#+OUTPUT: %s--intro.webm\n[[file:%s]]"
+ (plist-get talk :file-prefix)
(expand-file-name
- (concat (plist-get talk :slug) ".svg.png")
- (expand-file-name "in-between" emacsconf-stream-asset-dir))
- (emacsconf-pad-expand-intro talk))))
+ (concat (plist-get talk :slug) ".png")
+ (expand-file-name "in-between" emacsconf-stream-asset-dir)))))
(emacsconf-publish-prepare-for-display (emacsconf-get-talk-info))))))
(defvar-local emacsconf-subed-subtitle-source nil "Buffer with the intended subtitles.")
@@ -431,5 +435,14 @@ Create it if necessary."
map)
t))
+(defun emacsconf-subed-insert-question-heading-from-other-window ()
+ (interactive)
+ (insert
+ (with-selected-window
+ (other-window)
+ (replace-regexp-in-string
+ "^- +" "NOTE "
+ (buffer-substring (line-beginning-position) (line-end-position))))
+ "\n\n"))
(provide 'emacsconf-subed)
;;; emacsconf-subed.el ends here
diff --git a/emacsconf.el b/emacsconf.el
index 1a76a84..8f1da84 100644
--- a/emacsconf.el
+++ b/emacsconf.el
@@ -34,20 +34,23 @@
"Name of conference"
:group 'emacsconf
:type 'string)
-(defcustom emacsconf-year "2023"
+(defcustom emacsconf-year "2025"
"Conference year. String for easy inclusion."
:group 'emacsconf
:type 'string)
-(defcustom emacsconf-cfp-deadline "2023-09-14" "Deadline for proposals."
+(defcustom emacsconf-cfp-deadline "2025-09-19" "Target date for proposals."
:group 'emacsconf
:type 'string)
-(defcustom emacsconf-date "2023-12-02" "Starting date of EmacsConf."
+(defcustom emacsconf-date "2025-12-06" "Starting date of EmacsConf."
:group 'emacsconf
:type 'string)
-(defcustom emacsconf-video-target-date "2023-11-04" "Target date for receiving talk videos from the speakers."
+(defcustom emacsconf-dates "2025-12-06 to 2025-12-07" "Conference dates."
:group 'emacsconf
:type 'string)
-(defcustom emacsconf-schedule-announcement-date "2023-10-25" "Date for publishing the schedule."
+(defcustom emacsconf-video-target-date "2025-10-31" "Target date for receiving talk videos from the speakers."
+ :group 'emacsconf
+ :type 'string)
+(defcustom emacsconf-schedule-announcement-date "2025-10-24" "Date for publishing the schedule."
:group 'emacsconf
:type 'string)
(defcustom emacsconf-directory "~/vendor/emacsconf-wiki"
@@ -70,7 +73,7 @@
(defcustom emacsconf-base-url "https://emacsconf.org/" "Includes trailing slash"
:group 'emacsconf
:type 'string)
-(defcustom emacsconf-publishing-phase 'program
+(defcustom emacsconf-publishing-phase 'conference
"Controls what information to include.
'program - don't include times
'schedule - include times; use this leading up to the conference
@@ -95,11 +98,20 @@
(const tag "Prerec" 'prerec)
(const tag "Q&A harvesting" 'harvest)))
+(defcustom emacsconf-publish-include-pads t "When non-nil, include Etherpad info."
+ :group 'emacsconf
+ :type 'boolean)
+
(defcustom emacsconf-org-file nil
"Path to the Org file with emacsconference information."
:type 'file
:group 'emacsconf)
+(defcustom emacsconf-fallback-email "emacsconf-org-private@gnu.org"
+ "E-mail for public wiki pages if the speaker doesn't have a public email address."
+ :type 'string
+ :group 'emacsconf)
+
(defcustom emacsconf-upcoming-file nil
"Path to the Org file with upcoming talks."
:type 'file
@@ -125,8 +137,8 @@
(defvar emacsconf-chat-base "https://chat.emacsconf.org/")
(defvar emacsconf-backstage-dir "/ssh:orga@media.emacsconf.org:/var/www/media.emacsconf.org/2022/backstage")
(defvar emacsconf-upload-dir "/ssh:orga@media.emacsconf.org:/srv/upload")
-(defvar emacsconf-res-dir (format "/ssh:orga@res.emacsconf.org:/data/emacsconf/%s" emacsconf-year))
-(defvar emacsconf-media-extensions '("webm" "mkv" "mp4" "webm" "mov" "avi" "ts" "ogv" "wav" "ogg" "mp3" ))
+(defvar emacsconf-res-dir (format "/ssh:orga@res.emacsconf.org:/data/emacsconf/shared/%s" emacsconf-year))
+(defvar emacsconf-media-extensions '("webm" "mkv" "mp4" "m4v" "mov" "avi" "mpv" "ts" "ogv" "wav" "ogg" "mp3" ))
(defvar emacsconf-ftp-upload-dir "/ssh:orga@media.emacsconf.org:/srv/ftp/anon/upload-here")
(defvar emacsconf-backstage-user "emacsconf")
(defvar emacsconf-backstage-password nil "Password for backstage area.")
@@ -172,7 +184,13 @@
(defun emacsconf-backstage-dired ()
(interactive)
(dired emacsconf-backstage-dir "-tl"))
+
+(defun emacsconf-backstage-web ()
+ (interactive)
+ (browse-url (emacsconf-backstage-url)))
+
(defun emacsconf-res-dired () (interactive) (dired emacsconf-res-dir "-tl"))
+(defun emacsconf-res-cache-dired () (interactive) (dired (expand-file-name "cache" emacsconf-res-dir) "-tl"))
(defun emacsconf-media-dired () (interactive) (dired emacsconf-public-media-directory "-tl"))
(defun emacsconf-cache-dired ()
(interactive)
@@ -330,6 +348,61 @@ Return the list of new filenames."
dir)
t))))
+(defun emacsconf-upload-scp-from-json (talk key filename)
+ "Parse PsiTransfer JSON files and copy the uploaded file to the res directory.
+The file is associated with TALK. KEY identifies the file in a multi-file upload.
+FILENAME specifies an extra string to add to the file prefix if needed."
+ (interactive (let-alist (json-parse-string (buffer-string) :object-type 'alist)
+ (list (emacsconf-complete-talk-info)
+ .metadata.key
+ (read-string (format "Filename: ")))))
+ (let* ((source-dir (file-name-directory (buffer-file-name)))
+ (new-filename (concat (plist-get talk :file-prefix)
+ (if (string= filename "")
+ filename
+ (concat "--" filename))
+ "."
+ (let-alist (json-parse-string (buffer-string) :object-type 'alist)
+ (file-name-extension .metadata.name))))
+ (default-directory (expand-file-name "cache" emacsconf-res-dir))
+ (command (emacsconf-upload-scp-command-from-json talk key filename))
+ process)
+ (with-current-buffer (get-buffer-create (format "*scp %s*" (plist-get talk :slug)))
+ (insert (string-join command " ") "\n")
+ (set-process-sentinel
+ (apply #'start-process (concat "scp-" (plist-get talk :slug))
+ (current-buffer) nil
+ command)
+ (lambda (process event)
+ (when (string= event "finished")
+ (message "Finished copying %s"
+ new-filename)))))))
+
+(defun emacsconf-upload-scp-command-from-json (talk key filename)
+ "Parse PsiTransfer JSON files and get the SCP command for copying the uploaded file to the res directory.
+The file is associated with TALK. KEY identifies the file in a multi-file upload.
+FILENAME specifies an extra string to add to the file prefix if needed."
+ (interactive (let-alist (json-parse-string (buffer-string) :object-type 'alist)
+ (list (emacsconf-complete-talk-info)
+ .metadata.key
+ (read-string (format "Filename: ")))))
+ (let* ((source-dir (file-name-directory (buffer-file-name)))
+ (new-filename (concat (plist-get talk :file-prefix)
+ (if (string= filename "")
+ filename
+ (concat "--" filename))
+ "."
+ (let-alist (json-parse-string (buffer-string) :object-type 'alist)
+ (file-name-extension .metadata.name))))
+ (default-directory (expand-file-name "cache" emacsconf-res-dir))
+ (command (list
+ "scp"
+ (replace-regexp-in-string "^/ssh:" "" (expand-file-name key source-dir))
+ new-filename)))
+ (when (called-interactively-p 'any)
+ (kill-new (string-join command " ")))
+ command))
+
(defun emacsconf-upload-copy-from-json (talk key filename)
"Parse PsiTransfer JSON files and copy the uploaded file to the res directory.
The file is associated with TALK. KEY identifies the file in a multi-file upload.
@@ -394,6 +467,14 @@ FILENAME specifies an extra string to add to the file prefix if needed."
(setq value (or value (org-read-property-value prop)))
(org-entry-put (point) prop value))))
+(defun emacsconf-copy-property (search prop)
+ (interactive (list (emacsconf-complete-talk) nil))
+ (save-window-excursion
+ (emacsconf-with-talk-heading search
+ (setq prop (or prop (org-read-property-name)))
+ (when (called-interactively-p 'any)
+ (kill-new (org-entry-get (point) prop)))
+ (org-entry-get (point) prop))))
(defun emacsconf-complete-slug ()
(emacsconf-get-slug-from-string (emacsconf-complete-talk)))
@@ -410,10 +491,16 @@ FILENAME specifies an extra string to add to the file prefix if needed."
(format "[%s](%s \"%s\")" desc path (plist-get talk :title)))
(_ path))))
+(defun emacsconf-org-insert-description (link desc)
+ (unless desc
+ (when (string-match "emacsconf:\\(.+\\)" link)
+ (plist-get (emacsconf-search-talk-info (match-string 1 link)) :title))))
+
(with-eval-after-load 'org
(org-link-set-parameters
"emacsconf"
:follow #'emacsconf-go-to-talk
+ :insert-description #'emacsconf-org-insert-description
:complete (lambda () (concat "emacsconf:" (emacsconf-complete-slug)))
:export #'emacsconf-export-slug))
@@ -489,7 +576,9 @@ If INFO is specified, limit it to that list."
,@body))
(defvar emacsconf-status-types
- '(("WAITING_FOR_PREREC" . "Waiting for video from speaker")
+ '(("TO_ACCEPT" . "Waiting for video from speaker")
+ ("TO_CONFIRM" . "Waiting for video from speaker")
+ ("WAITING_FOR_PREREC" . "Waiting for video from speaker")
("TO_PROCESS" . "Processing uploaded video")
("PROCESSING" . "Processing uploaded video")
("TO_AUTOCAP" . "Processing uploaded video")
@@ -513,6 +602,11 @@ If INFO is specified, limit it to that list."
(setq list (cons (match-string-no-properties 0) list)))
(plist-put o :categories (reverse list))))
+(defun emacsconf-talk-recorded-p (talk)
+ "Returns non-nil if TALK will start with a recorded video."
+ (and (not (plist-get talk :live))
+ (plist-get talk :video-file)))
+
(defun emacsconf-get-talk-info-from-properties (o)
(let ((heading (org-heading-components))
(field-props '(
@@ -544,6 +638,8 @@ If INFO is specified, limit it to that list."
(:prerec-info "PREREC_INFO")
;; Prep
(:bbb-room "ROOM")
+ (:bbb-mod-code "BBB_MOD_CODE")
+ (:emailed-schedule "EMAILED_SCHEDULE")
;; Processing
(:file-prefix "FILE_PREFIX")
(:video-file "VIDEO_FILE")
@@ -559,6 +655,7 @@ If INFO is specified, limit it to that list."
(:caption-note "CAPTION_NOTE")
(:captions-edited "CAPTIONS_EDITED")
;; Conference
+ (:live "LIVE")
(:check-in "CHECK_IN")
(:public "PUBLIC")
(:intro-note "INTRO_NOTE")
@@ -574,7 +671,7 @@ If INFO is specified, limit it to that list."
(:present "PRESENT")
(:talk-id "TALK_ID") ; use slug instead
(:qa-public "QA_PUBLIC") ; now tracked by the OPEN_Q and UNSTREAMED_Q status
- (:uuid "UUID") ; Pentabarf export
+ (:uuid "UUID") ; Pentabarf export
)))
(apply
'append
@@ -685,7 +782,10 @@ The subheading should match `emacsconf-abstract-heading-regexp'."
")")))
(defun emacsconf-get-abstract-from-wiki (o)
- (plist-put o :markdown (emacsconf-talk-markdown-from-wiki (plist-get o :slug))))
+ (plist-put o :markdown (emacsconf-talk-markdown-from-wiki (plist-get o :slug)))
+ (when (plist-get o :markdown)
+ (plist-put o :org-description
+ (pandoc-convert-stdio (plist-get o :markdown) "markdown" "org"))))
(defun emacsconf-add-talk-status (o)
@@ -751,7 +851,8 @@ The subheading should match `emacsconf-abstract-heading-regexp'."
emacsconf-add-timezone-conversions
emacsconf-add-speakers-with-pronouns
emacsconf-add-live-info
- emacsconf-add-video-info)
+ emacsconf-add-video-info
+ emacsconf-add-media-info)
"Functions to collect information.")
(defun emacsconf-add-speakers-with-pronouns (o)
@@ -810,6 +911,15 @@ The subheading should match `emacsconf-abstract-heading-regexp'."
(list "youtube" "qa-youtube" "toobnix" "qa-toobnix"))
o)
+(defun emacsconf-add-media-info (o)
+ (plist-put o
+ :video-url (format "%s%s/%s--main.webm" emacsconf-media-base-url emacsconf-year (plist-get o :file-prefix)))
+ (plist-put o
+ :captions-url (format "%s%s/%s--main.vtt" emacsconf-media-base-url emacsconf-year (plist-get o :file-prefix)))
+ (plist-put o
+ :audio-url (format "%s%s/%s--main.opus" emacsconf-media-base-url emacsconf-year (plist-get o :file-prefix)))
+ 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"
@@ -879,10 +989,7 @@ The subheading should match `emacsconf-abstract-heading-regexp'."
(plist-put o :qa-link "none")
(plist-put o :qa-backstage-url (plist-get o :pad-url))))
(plist-put o :recorded-intro
- (let ((filename
- (expand-file-name (concat (plist-get o :slug) ".webm")
- (expand-file-name "intros" emacsconf-stream-asset-dir))))
- (and (file-exists-p filename) filename)))
+ (emacsconf-talk-file o "--intro.webm"))
o))
(defun emacsconf-search-talk-info (search &optional info)
@@ -952,13 +1059,8 @@ The subheading should match `emacsconf-abstract-heading-regexp'."
(defun emacsconf-get-talk-info-from-file (&optional filename)
- (with-temp-buffer
- (insert-file-contents (or filename "conf.org"))
- (org-mode)
- (org-show-all)
- (goto-char (point-min))
- (goto-char (org-find-property "ID" "talks"))
- (emacsconf-get-talk-info 'wiki)))
+ (let ((emacsconf-org-file filename))
+ (emacsconf-get-talk-info)))
(defun emacsconf-include-next-talks (info number)
(let* ((info (emacsconf-publish-prepare-for-display info))
@@ -984,6 +1086,12 @@ The subheading should match `emacsconf-abstract-heading-regexp'."
"Return the plist for TALK."
(if (stringp talk) (emacsconf-find-talk-info talk info) talk))
+(defun emacsconf-resolve-shift (shift)
+ "Return the plist for SHIFT."
+ (if (stringp shift) (seq-find (lambda (o) (string= (plist-get o :id) shift))
+ emacsconf-shifts)
+ shift))
+
(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)))
@@ -1083,15 +1191,25 @@ The subheading should match `emacsconf-abstract-heading-regexp'."
(interactive (list (emacsconf-complete-talk)))
(insert (plist-get (emacsconf-search-talk-info search) :title)))
+(defun emacsconf-insert-talk-schedule (search)
+ "Insert the schedule for SEARCH."
+ (interactive (list (emacsconf-complete-talk)))
+ (insert (emacsconf-mail-format-talk-schedule (emacsconf-search-talk-info search))))
+
(defun emacsconf-insert-talk-email (search)
"Insert the talk email matching SEARCH."
(interactive (list (emacsconf-complete-talk)))
(insert (plist-get (emacsconf-search-talk-info search) :email)))
+(defun emacsconf-insert-talk-link (search)
+ "Insert the talk link matching SEARCH."
+ (interactive (list (emacsconf-complete-talk)))
+ (insert (concat emacsconf-base-url "/" (plist-get (emacsconf-search-talk-info search) :url))))
+
(defun emacsconf-backstage-url (&optional base-url)
"Return or insert backstage URL with credentials."
- (setq base-url (or base-url (concat emacsconf-media-base-url emacsconf-year "/backstage/")))
(interactive)
+ (setq base-url (or base-url (concat emacsconf-media-base-url emacsconf-year "/backstage/")))
(let ((url (format
"https://%s:%s@%s"
emacsconf-backstage-user
@@ -1115,11 +1233,14 @@ The subheading should match `emacsconf-abstract-heading-regexp'."
:doc "Keymap for emacsconf-related things"
"a" #'emacsconf-announce
"i e" #'emacsconf-insert-talk-email
+ "i l" #'emacsconf-insert-talk-link
"i t" #'emacsconf-insert-talk-title
+ "i s" #'emacsconf-insert-talk-schedule
"I" #'emacsconf-message-talk-info
"c" #'emacsconf-find-captions-from-slug
"d" #'emacsconf-find-caption-directives-from-slug
"p" #'emacsconf-set-property-from-slug
+ "P" #'emacsconf-copy-property
"w" #'emacsconf-edit-wiki-page
"s" #'emacsconf-set-start-time-for-slug
"W" #'emacsconf-browse-wiki-page
@@ -1362,6 +1483,7 @@ If TIMEZONES is a string, split it by commas."
'((emacsconf-pad-api-key . etherpad_api_key)
(emacsconf-pad-base . etherpad_url)
(emacsconf-backstage-password . emacsconf_backstage_password))))))
+(defvar emacsconf-live-base-url "https://live.emacsconf.org/")
;; (emacsconf-ansible-load-vars (expand-file-name "prod-vars.yml" emacsconf-ansible-directory))
;;; Tracks
(defvar emacsconf-tracks
@@ -1371,30 +1493,30 @@ If TIMEZONES is a string, split it by commas."
:webchat-url "https://chat.emacsconf.org/?join=emacsconf,emacsconf-org,emacsconf-accessible,emacsconf-dev,emacsconf-gen"
:stream ,(concat emacsconf-stream-base "gen.webm")
:480p ,(concat emacsconf-stream-base "gen-480p.webm")
- ;; :youtube-url "https://www.youtube.com/watch?v=UEJ88c7MJq0"
- ;; :youtube-studio-url "https://studio.youtube.com/video/UEJ88c7MJq0/livestreaming"
- ;; :toobnix-url "https://toobnix.org/w/7t9X8eXuSby8YpyEKTb4aj"
+ :youtube-url "https://www.youtube.com/live/FI3eGeGCyQM"
+ :youtube-studio-url "https://studio.youtube.com/video/FI3eGeGCyQM/livestreaming"
+ :toobnix-url "https://toobnix.org/w/oLwaPU7MgMFDAWPhaFdW1t"
:start "09:00" :end "17:00"
:uid 2002
:vnc-display ":5"
:vnc-port "5905"
:autopilot crontab
:status "online")
- (:name "Development" :color "skyblue" :id "dev" :channel "emacsconf-dev"
- :watch ,(format "https://live.emacsconf.org/%s/watch/dev/" emacsconf-year)
- :webchat-url "https://chat.emacsconf.org/?join=emacsconf,emacsconf-org,emacsconf-accessible,emacsconf-gen,emacsconf-dev"
- :tramp "/ssh:emacsconf-dev@res.emacsconf.org#46668:"
- ;; :toobnix-url "https://toobnix.org/w/w6K77y3bNMo8xsNuqQeCcD"
- ;; :youtube-url "https://www.youtube.com/watch?v=PMaoF-xa1b4"
- ;; :youtube-studio-url "https://studio.youtube.com/video/PMaoF-xa1b4/livestreaming"
- :stream ,(concat emacsconf-stream-base "dev.webm")
- :480p ,(concat emacsconf-stream-base "dev-480p.webm")
- :uid 2003
- :start "10:00" :end "17:00"
- :vnc-display ":6"
- :vnc-port "5906"
- :autopilot crontab
- :status "online")))
+ (:name "Development" :color "skyblue" :id "dev" :channel "emacsconf-dev"
+ :watch ,(format "https://live.emacsconf.org/%s/watch/dev/" emacsconf-year)
+ :webchat-url "https://chat.emacsconf.org/?join=emacsconf,emacsconf-org,emacsconf-accessible,emacsconf-gen,emacsconf-de"
+ :tramp "/ssh:emacsconf-dev@res.emacsconf.org#46668:"
+ :toobnix-url "https://toobnix.org/w/uXGmcRigZD82UWr5nehKeL"
+ :youtube-url "https://youtube.com/live/KCZthyBhHtg"
+ :youtube-studio-url "https://studio.youtube.com/video/KCZthyBhHtg/livestreaming"
+ :stream ,(concat emacsconf-stream-base "dev.webm")
+ :480p ,(concat emacsconf-stream-base "dev-480p.webm")
+ :uid 2003
+ :start "10:00" :end "17:00"
+ :vnc-display ":6"
+ :vnc-port "5906"
+ :autopilot crontab
+ :status "offline")))
(defun emacsconf-get-track (name)
"Get the track for NAME.
@@ -1458,8 +1580,7 @@ NAME could be a track name, a talk name, or a list."
info)
info))
-(defvar emacsconf-shifts
- (list (list :id "sat-am-gen" :track "General" :start "2023-12-02T08:00:00-0500" :end "2023-12-02T12:00:00-0500" :host "zaeph" :streamer "sachac" :checkin "FlowyCoder" :coord "sachac") (list :id "sat-pm-gen" :track "General" :start "2023-12-02T13:00:00-0500" :end "2023-12-02T18:00:00-0500" :host "zaph" :streamer "sachac" :checkin "FlowyCoder" :coord "sachac") (list :id "sat-am-dev" :track "Development" :start "2023-12-02T08:00:00-0500" :end "2023-12-02T12:00:00-0500" :host "bandali" :streamer "sachac" :checkin "FlowyCoder" :coord "sachac") (list :id "sat-pm-dev" :track "Development" :start "2023-12-02T13:00:00-0500" :end "2023-12-02T18:00:00-0500" :host "bandali" :streamer "sachac" :checkin "FlowyCoder" :coord "sachac") (list :id "sun-am-gen" :track "General" :start "2023-12-03T08:00:00-0500" :end "2023-12-03T12:00:00-0500" :host "zaeph" :streamer "sachac" :checkin "FlowyCoder" :coord "sachac") (list :id "sun-pm-gen" :track "General" :start "2023-12-03T13:00:00-0500" :end "2023-12-03T18:00:00-0500" :host "zaeph" :streamer "sachac" :checkin "FlowyCoder" :coord "sachac") (list :id "sun-am-dev" :track "Development" :start "2023-12-03T08:00:00-0500" :end "2023-12-03T12:00:00-0500" :host "bandali" :streamer "sachac" :checkin "FlowyCoder" :coord "sachac") (list :id "sun-pm-dev" :track "Development" :start "2023-12-03T13:00:00-0500" :end "2023-12-03T18:00:00-0500" :host "bandali" :streamer "sachac" :checkin "FlowyCoder" :coord "sachac")))
+(setq emacsconf-shifts (list (list :id "sat-am-gen" :track "General" :start "2025-12-06T09:00:00-0500" :end "2025-12-06T12:00:00-0500" :host "zaeph" :streamer "sachac" :checkin "sachac" :coord "sachac") (list :id "sat-pm-gen" :track "General" :start "2025-12-06T13:00:00-0500" :end "2025-12-06T17:00:00-0500" :host "zaeph" :streamer "sachac" :checkin "sachac" :coord "sachac") (list :id "sat-am-dev" :track "Development" :start "2025-12-06T10:00:00-0500" :end "2025-12-06T12:00:00-0500" :host "corwin" :streamer "sachac" :checkin "sachac" :coord "sachac") (list :id "sat-pm-dev" :track "Development" :start "2025-12-06T13:00:00-0500" :end "2025-12-06T17:00:00-0500" :host "corwin" :streamer "sachac" :checkin "sachac" :coord "sachac") (list :id "sun-am-gen" :track "General" :start "2025-12-07T09:00:00-0500" :end "2025-12-07T12:00:00-0500" :host "zaeph/corwin" :streamer "sachac" :checkin "sachac" :coord "sachac") (list :id "sun-pm-gen" :track "General" :start "2025-12-07T13:00:00-0500" :end "2025-12-07T17:00:00-0500" :host "zaeph/corwin" :streamer "sachac" :checkin "sachac" :coord "sachac")))
(defun emacsconf-filter-talks-by-time (start-time end-time info)
"Return talks that are between START-TIME and END-TIME (inclusive) in INFO."
@@ -1531,6 +1652,101 @@ Filter by TRACK if given. Use INFO as the list of talks."
(seq-group-by (lambda (o) (plist-get o :speakers))
(or info (emacsconf-active-talks (emacsconf-filter-talks (emacsconf-get-talk-info))))))))
+(defun emacsconf-bbb-create-rooms ()
+ "Copy the commands needed to create the rooms.
+docker exec -it greenlight-v3 /bin/bash -c \"bundle exec rails console\"
+user_id = User.find_by_email(\"emacsconf@sachachua.com\").id"
+ (interactive)
+ (kill-new
+ (mapconcat (lambda (group)
+ (format
+ "Room.create(user_id: user_id, name: \"%s - %s\")\n"
+ (plist-get (cadr group) :speakers)
+ (string-join (mapcar (lambda (talk) (plist-get talk :slug))
+ (cdr group))
+ ", ")))
+ (emacsconf-mail-groups (emacsconf-active-talks (emacsconf-get-talk-info)))
+ ""))
+ (message "Copied. Run it inside the greenlight-v3 rails console."))
+
+(defun emacsconf-load-rooms (string)
+ "Split STRING and load them as ROOM properties.
+STRING should be a list of rooms, one room per line, like this:
+friendly-id speaker - slugs
+friendly-id speaker - slugs
+
+Print out room IDs with:
+Room.all.each { |x| puts x.friendly_id + " " + x.name }; nil
+"
+ (interactive "MInput: ")
+ (let ((rooms
+ (mapcar (lambda (row) (when (string-match "^\\(.+?\\) \\(.+\\)" row)
+ (list (match-string 1 row) (match-string 2 row))))
+ (split-string string "\n"))))
+ (mapc (lambda (talk)
+ (emacsconf-go-to-talk talk)
+ (when (plist-get talk :speakers)
+ (org-entry-put
+ (point)
+ "ROOM"
+ (concat
+ emacsconf-bbb-base-url
+ "rooms/"
+ (car
+ (seq-find
+ (lambda (o)
+ (string-match
+ (concat
+ "^"
+ (regexp-quote
+ (plist-get talk :speakers))
+ " - ")
+ (cadr o)))
+ rooms))
+ "/join"))))
+ (emacsconf-active-talks (emacsconf-get-talk-info)))))
+
+(defun emacsconf-bbb-spookfox-set-moderator-codes ()
+ (interactive)
+ (dolist (talk (seq-filter (lambda (o)
+ (and (plist-get o :bbb-room)
+ (not (plist-get o :bbb-mod-code))))
+ (emacsconf-publish-prepare-for-display (emacsconf-get-talk-info))))
+ (spookfox-js-injection-eval-in-active-tab
+ (format "window.location.href = \"%s\""
+ (replace-regexp-in-string "/join" "" (plist-get talk :bbb-room)))
+ t)
+ (sleep-for 3)
+ (spookfox-js-injection-eval-in-active-tab
+ "document.querySelector('button[data-rr-ui-event-key=\"settings\"]').click()" t)
+ (spookfox-js-injection-eval-in-active-tab
+ "document.querySelector('input#glAnyoneCanStart').checked = true")
+ (spookfox-js-injection-eval-in-active-tab
+ "document.querySelector('input#muteOnStart').checked = true")
+ (spookfox-js-injection-eval-in-active-tab
+ "document.querySelectorAll('.border-end button')[2].click()" t)
+ (let ((code (spookfox-js-injection-eval-in-active-tab
+ "document.querySelector('.access-code-input input').value" t)))
+ (message "Setting %s to %s" (plist-get talk :slug) code)
+ (emacsconf-set-property-from-slug
+ talk "BBB_MOD_CODE"
+ code)
+ (sit-for 2))))
+
+(defun emacsconf-bbb-spookfox-confirm-settings ()
+ (interactive)
+ (dolist (talk (seq-filter (lambda (o)
+ (plist-get o :bbb-room))
+ (emacsconf-publish-prepare-for-display (emacsconf-get-talk-info))))
+ (spookfox-js-injection-eval-in-active-tab
+ (format "window.location.href = \"%s\""
+ (replace-regexp-in-string "/join" "" (plist-get talk :bbb-room)))
+ t)
+ (sleep-for 3)
+ (spookfox-js-injection-eval-in-active-tab
+ "document.querySelector('button[data-rr-ui-event-key=\"settings\"]').click()" t)
+ (sleep-for 3)))
+
(defun emacsconf-surround (before text after &optional alternative)
"Concat BEFORE, TEXT, and AFTER if TEXT is specified, or return ALTERNATIVE."
(if (and text (not (string= text "")))
@@ -1538,10 +1754,12 @@ Filter by TRACK if given. Use INFO as the list of talks."
alternative))
;;; Volunteer management
-(defun emacsconf-get-volunteer-info ()
+(defun emacsconf-get-volunteer-info (&optional tag)
(with-current-buffer (find-file-noselect emacsconf-org-file)
(org-map-entries (lambda () (org-entry-properties))
- "volunteer+EMAIL={.}")))
+ (format "volunteer+EMAIL={.}%s"
+ (if tag (concat "+" tag)
+ "")))))
(defun emacsconf-volunteer-emails-for-completion (&optional info)
(mapcar (lambda (o)
@@ -1555,16 +1773,8 @@ Filter by TRACK if given. Use INFO as the list of talks."
(defun emacsconf-volunteer-insert-email (&optional info)
(interactive)
- (insert (completing-read
- (mapcar
- (lambda (o)
- (emacsconf-surround
- (if (assoc-default "ITEM" o)
- (concat (assoc-default "ITEM" o) " <")
- "<")
- (assoc-default "EMAIL" o)
- ">" ""))
- (or info (emacsconf-get-volunteer-info))))))
+ (insert (assoc-default "EMAIL" (emacsconf-complete-volunteer info) #'string=)))
+
(defun emacsconf-complete-volunteer (&optional info)
(setq info (or info (emacsconf-get-volunteer-info)))
(let* ((choices
@@ -1627,19 +1837,21 @@ Filter by TRACK if given. Use INFO as the list of talks."
(defun emacsconf-add-org-after-todo-state-change-hook ()
"Add FUNC to `org-after-todo-stage-change-hook'."
(interactive)
- (with-current-buffer (find-buffer-visiting emacsconf-org-file)
+ (with-current-buffer (or (find-buffer-visiting emacsconf-org-file)
+ (find-file-noselect emacsconf-org-file))
(add-hook 'org-after-todo-state-change-hook #'emacsconf-org-after-todo-state-change nil t)))
(defun emacsconf-remove-org-after-todo-state-change-hook ()
"Remove FUNC from `org-after-todo-stage-change-hook'."
(interactive)
- (with-current-buffer (find-buffer-visiting emacsconf-org-file)
+ (with-current-buffer (or (find-buffer-visiting emacsconf-org-file)
+ (find-file-noselect emacsconf-org-file))
(remove-hook 'org-after-todo-state-change-hook
#'emacsconf-org-after-todo-state-change t)))
(defvar emacsconf-todo-hooks
- '(emacsconf-stream-play-talk-on-change
- emacsconf-stream-open-qa-windows-on-change
+ '(;; emacsconf-stream-play-talk-on-change
+ ;; emacsconf-stream-open-qa-windows-on-change
emacsconf-erc-announce-on-change ;; announce via ERC
emacsconf-publish-bbb-redirect
emacsconf-stream-update-talk-info-on-change
@@ -1704,6 +1916,7 @@ tracks with the ID in the cdr of that list."
"Add NOTE as a logbook entry for the current subtree."
(move-marker org-log-note-return-to (point))
(move-marker org-log-note-marker (point))
+ (setq org-log-note-window-configuration (current-window-configuration))
(with-temp-buffer
(insert note)
(let ((org-log-note-purpose 'note))
@@ -1712,9 +1925,11 @@ tracks with the ID in the cdr of that list."
(defun emacsconf-add-to-talk-logbook (talk note)
"Add NOTE as a logbook entry for TALK."
(interactive (list (emacsconf-complete-talk) (read-string "Note: ")))
- (save-excursion
- (emacsconf-with-talk-heading talk
- (emacsconf-add-to-logbook note))))
+ (save-window-excursion
+ (save-excursion
+ (emacsconf-with-talk-heading talk
+ (emacsconf-add-to-logbook note)
+ (bury-buffer (current-buffer))))))
(defun emacsconf-reload ()
"Reload the emacsconf-el modules."
@@ -1861,5 +2076,104 @@ tracks with the ID in the cdr of that list."
(concat "ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "
(shell-quote-argument (expand-file-name filename)))))))
+(defun emacsconf-delete-from-all (files)
+ "Delete FILES from all the directories."
+ (interactive (list (dired-get-marked-files)))
+ (dolist (dir (list emacsconf-cache-dir emacsconf-backstage-dir
+ (expand-file-name "cache" emacsconf-res-dir)
+ emacsconf-public-media-directory))
+ (dolist (file files)
+ (when (and dir (file-exists-p (expand-file-name (file-name-nondirectory file)
+ dir)))
+ (delete-file (expand-file-name (file-name-nondirectory file)
+ dir))
+ (message "Deleting %s from %s"
+ (file-name-nondirectory file) dir)))))
+
+(defun emacsconf-current-org-notebook-filename ()
+ "Return the filename for the current year's public organizers notebook."
+ (expand-file-name "organizers-notebook/index.org" (expand-file-name emacsconf-year emacsconf-directory)))
+
+(defun emacsconf-current-org-notebook-open (&optional common)
+ "Open the current year's public organizers notebook.
+With a prefix argument (\\[universal-argument]), open the general organizers notebook."
+ (interactive (list current-prefix-arg))
+ (find-file (if common
+ (expand-file-name "organizers-notebook/index.org"
+ emacsconf-directory)
+ (emacsconf-current-org-notebook-filename))))
+
+(defun emacsconf-current-org-notebook-heading (&optional common)
+ "Open the current year's public organizers notebook and jump to a heading.
+With a prefix argument (\\[universal-argument]), open the general organizers notebook."
+ (interactive (list current-prefix-arg))
+ (emacsconf-current-org-notebook-open common)
+ (cond
+ ((fboundp 'consult-org-heading)
+ (call-interactively #'consult-org-heading))
+ (t (call-interactively #'org-goto))))
+
+(defun emacsconf-main-org-notebook-heading (&optional other)
+ "Open the main public organizers notebook and jump to a heading.
+With a prefix argument (\\[universal-argument]), open this year's notebook."
+ (interactive (list current-prefix-arg))
+ (emacsconf-current-org-notebook-heading (not other)))
+
+(defvar emacsconf-refresh-schedule-from-org nil "Non-nil means refresh the schedule from the organizer notebook.")
+(defun emacsconf-current-org-notebook-refresh-schedule ()
+ "Refresh info from draft schedule."
+ (interactive)
+ (when emacsconf-refresh-schedule-from-org
+ (save-window-excursion
+ (with-current-buffer (find-file-noselect (emacsconf-current-org-notebook-filename))
+ (save-restriction
+ (widen)
+ (goto-char (org-find-property "CUSTOM_ID" "draft-schedule"))
+ (org-babel-execute-subtree))))))
+
+(defun emacsconf-insert-availability-comment (talk)
+ (interactive (list (or (emacsconf-search-talk-info (thing-at-point 'symbol))
+ (emacsconf-complete-talk))))
+ (save-excursion
+ (goto-char (line-end-position))
+ (insert " ; " (plist-get talk :availability))))
+
+(defun emacsconf-cancel-talk (talk)
+ "Cancel TALK. Assume that the schedule has already been updated."
+ (interactive (list (emacsconf-complete-talk)))
+ (emacsconf-with-talk-heading talk
+ (org-todo "CANCELLED")
+ (emacsconf-current-org-notebook-refresh-schedule)
+ (emacsconf-schedule-update-from-info)
+ (emacsconf-update-schedule)
+ (emacsconf-publish-watch-pages)
+ (emacsconf-publish-talks-json-to-files)
+ (emacsconf-publish-info-pages-for-talk talk)
+ (emacsconf-publish-info-pages)
+ (emacsconf-stream-generate-in-between-pages)
+ ;; Regenerate intros
+ (find-file (emacsconf-latest-file (expand-file-name "../assets/intros" emacsconf-cache-dir) "^\\(intro\\|script\\).*.vtt"))
+ (message "Remember to regenerate the intro videos.")))
+
+(defun emacsconf-rescheduled-talks (&optional info)
+ "See `emacsconf-schedule-save-emailed-times' and `emacsconf-mail-schedule-updates'."
+ (seq-filter
+ (lambda (o)
+ (and (plist-get o :email)
+ (plist-get o :qa-type)
+ (not (string= (plist-get o :emailed-schedule)
+ (replace-regexp-in-string "[<>]" "" (plist-get o :scheduled))))
+ (not (string= (plist-get o :qa-type) "none"))
+ (not (= (emacsconf-schedule-difference-from-emailed o) 0))))
+ (emacsconf-publish-prepare-for-display (emacsconf-filter-talks (or info (emacsconf-get-talk-info))))))
+
+(defun emacsconf-schedule-difference-from-emailed (talk)
+ "Return the number of minutes. Negative means earlier."
+ (let ((start (plist-get talk :start-time))
+ (emailed (org-timestamp-to-time (org-timestamp-split-range
+ (org-timestamp-from-string
+ (format "<%s>" (plist-get talk :emailed-schedule)))))))
+ (round (/ (float-time (time-subtract start emailed)) 60.0))))
+
(provide 'emacsconf)
;;; emacsconf.el ends here