summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--emacsconf-erc.el41
-rw-r--r--emacsconf-extract.el325
-rw-r--r--emacsconf-mail.el261
-rw-r--r--emacsconf-pad.el7
-rw-r--r--emacsconf-publish.el194
-rw-r--r--emacsconf-subed.el21
-rw-r--r--emacsconf.el34
7 files changed, 726 insertions, 157 deletions
diff --git a/emacsconf-erc.el b/emacsconf-erc.el
index b01e3f6..6139645 100644
--- a/emacsconf-erc.el
+++ b/emacsconf-erc.el
@@ -68,12 +68,12 @@
(defcustom emacsconf-erc-org "#emacsconf-org" "Channel for organizers")
(defcustom emacsconf-topic-templates
- '(("#emacsconf" "Welcome to EmacsConf 2024 | 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/2024/watch/gen/ | Subscribe to https://lists.gnu.org/mailman/listinfo/emacsconf-discuss for updates")
- ("#emacsconf-dev" "Development track | https://emacsconf.org/2024/watch/dev/ | Subscribe to https://lists.gnu.org/mailman/listinfo/emacsconf-discuss for updates")
- ("#emacsconf-accessible" "EmacsConf 2024 accessibility - help by describing what's happening | Subscribe to https://lists.gnu.org/mailman/listinfo/emacsconf-discuss for updates")
- ("#emacsconf-org" "EmacsConf 2024 | 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 2024 | 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")
@@ -547,5 +547,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-mail.el b/emacsconf-mail.el
index 23743e8..b48cead 100644
--- a/emacsconf-mail.el
+++ b/emacsconf-mail.el
@@ -253,7 +253,9 @@ insert into the current buffer instead of drafting e-mails."
(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)
@@ -581,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
@@ -622,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. =)
@@ -1074,6 +1092,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)))
@@ -1124,7 +1196,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) "-"))
@@ -1436,7 +1510,7 @@ ${signature}"))
(plist-get talk :slug)))
(cdr group)
" , ")
- :upload-url (concat "https://ftp-upload.emacsconf.org/?sid="
+ :upload-url (concat "https://upload.emacsconf.org/?sid="
emacsconf-upload-password
"-"
(mapconcat (lambda (o) (plist-get o :slug)) (cdr group) "-"))
@@ -1775,7 +1849,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
@@ -1788,8 +1862,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}!
@@ -1798,18 +1872,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
@@ -1830,6 +1908,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)
@@ -2014,6 +2093,147 @@ ${signature}
(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 (subject &optional groups)
@@ -2175,10 +2395,17 @@ 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 emacsconf-submit-email))
+ (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: ")
diff --git a/emacsconf-pad.el b/emacsconf-pad.el
index 6a79d45..0a958e1 100644
--- a/emacsconf-pad.el
+++ b/emacsconf-pad.el
@@ -177,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
@@ -198,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>
@@ -366,7 +368,7 @@ ${next-talk-list}
(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 "checkin-" (downcase (format-time-string "%a" (date-to-time (plist-get shift :start)))))))
+ :checkin-pad (concat emacsconf-pad-base "private-" emacsconf-private-pad-prefix "-checkin-" (downcase (format-time-string "%a" (date-to-time (plist-get shift :start)))))))
(shift-talks
(mapcar (lambda (o) (append prefixed o))
(seq-filter
@@ -498,6 +500,7 @@ ${next-talk-list}
<ul><li>[ ] Window or screen can be shared
<li>[ ] Text is readable</li></ul>
<li>[ ] Webcam sharing (optional)</li></ul></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>
diff --git a/emacsconf-publish.el b/emacsconf-publish.el
index 31ff72b..3204da9 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)
@@ -145,13 +145,14 @@
(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) "")
@@ -161,7 +162,7 @@
: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)
@@ -364,10 +365,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)
@@ -428,6 +429,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>"
@@ -457,7 +463,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)"
@@ -469,9 +475,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)
@@ -563,8 +575,10 @@ ${categories}
(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")
@@ -605,7 +619,10 @@ ${categories}
(emacsconf-surround " <" (and (member emacsconf-publishing-phase '(schedule conference))
(plist-get o :qa-url)) ">" ""))
(concat (or (plist-get o :video-time)
- (plist-get o :time)) "-min talk cancelled"))
+ (plist-get o :time))
+ (if (string= (plist-get o :status) "CANCELLED")
+ "-min talk cancelled"
+ "-min talk")))
:pad-info
(if (and talk-p emacsconf-publish-include-pads (not (and (member emacsconf-publishing-phase '(schedule conference))
(string= (plist-get o :qa-type) "etherpad"))))
@@ -720,7 +737,7 @@ 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)))
@@ -743,7 +760,7 @@ This includes the intro note, the schedule, and talk resources."
(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)))
@@ -763,6 +780,7 @@ 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)
+ (setq video-id (or video-id "mainVideo"))
(let* ((subtitles
(subed-parse-file (if lang
(format "%s_%s.vtt"
@@ -771,14 +789,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))
@@ -827,12 +845,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")
@@ -1180,7 +1199,7 @@ You can also get this schedule as iCalendar files: ${icals}. Importing that into
(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)
+ ((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))
@@ -1278,6 +1297,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))
@@ -1574,7 +1595,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" "backstage")))
+ (or "reencoded" "normalized" "final" "old" "bbb" "backstage" "pad" "silences")))
nil)
((rx ".diff") nil)
((rx "--original")
@@ -2653,17 +2674,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
@@ -2814,6 +2837,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
@@ -3020,5 +3046,119 @@ Tends to be quota-limited, though."
(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-subed.el b/emacsconf-subed.el
index fafdf38..422aec3 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."
@@ -431,5 +429,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 fe2a30c..957ad2a 100644
--- a/emacsconf.el
+++ b/emacsconf.el
@@ -34,20 +34,20 @@
"Name of conference"
:group 'emacsconf
:type 'string)
-(defcustom emacsconf-year "2024"
+(defcustom emacsconf-year "2025"
"Conference year. String for easy inclusion."
:group 'emacsconf
:type 'string)
-(defcustom emacsconf-cfp-deadline "2024-09-20" "Deadline for proposals."
+(defcustom emacsconf-cfp-deadline "2025-09-19" "Target date for proposals."
:group 'emacsconf
:type 'string)
-(defcustom emacsconf-date "2024-12-07" "Starting date of EmacsConf."
+(defcustom emacsconf-date "2025-12-06" "Starting date of EmacsConf."
:group 'emacsconf
:type 'string)
-(defcustom emacsconf-video-target-date "2024-11-08" "Target date for receiving talk videos from the speakers."
+(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 "2024-10-25" "Date for publishing the schedule."
+(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 +70,7 @@
(defcustom emacsconf-base-url "https://emacsconf.org/" "Includes trailing slash"
:group 'emacsconf
:type 'string)
-(defcustom emacsconf-publishing-phase 'conference
+(defcustom emacsconf-publishing-phase 'resources
"Controls what information to include.
'program - don't include times
'schedule - include times; use this leading up to the conference
@@ -86,7 +86,7 @@
(const :tag "Harvest: Extracting info" conference)
(const :tag "Resources: Don't include status, publish all Q&A" resources)))
-(defcustom emacsconf-backstage-phase 'prerec
+(defcustom emacsconf-backstage-phase 'harvest
"Contros what information to include backstage.
'prerec - focus on captioning
'harvest - focus on Q&A."
@@ -1024,13 +1024,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))
@@ -1171,6 +1166,11 @@ The subheading should match `emacsconf-abstract-heading-regexp'."
(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."
(interactive)
@@ -1198,6 +1198,7 @@ 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
@@ -1465,7 +1466,7 @@ If TIMEZONES is a string, split it by commas."
:vnc-display ":5"
:vnc-port "5905"
:autopilot crontab
- :status "offline")
+ :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"
@@ -1544,8 +1545,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 "2024-12-07T09:00:00-0500" :end "2024-12-07T12:00:00-0500" :host "zaeph" :streamer "sachac" :checkin "sachac" :coord "sachac") (list :id "sat-pm-gen" :track "General" :start "2024-12-07T13:00:00-0500" :end "2024-12-07T17:00:00-0500" :host "zaeph" :streamer "sachac" :checkin "sachac" :coord "sachac") (list :id "sat-am-dev" :track "Development" :start "2024-12-07T10:00:00-0500" :end "2024-12-07T12:00:00-0500" :host "corwin" :streamer "sachac" :checkin "sachac" :coord "sachac") (list :id "sat-pm-dev" :track "Development" :start "2024-12-07T13:00:00-0500" :end "2024-12-07T17:00:00-0500" :host "corwin" :streamer "sachac" :checkin "sachac" :coord "sachac") (list :id "sun-am-gen" :track "General" :start "2024-12-08T09:00:00-0500" :end "2024-12-08T12:00:00-0500" :host "zaeph" :streamer "sachac" :checkin "corwin" :coord "sachac") (list :id "sun-pm-gen" :track "General" :start "2024-12-08T13:00:00-0500" :end "2024-12-08T17:00:00-0500" :host "zaeph" :streamer "sachac" :checkin "corwin" :coord "sachac")))
+(setq emacsconf-shifts (list (list :id "sat-am-gen" :track "General" :start "2025-12-07T09:00:00-0500" :end "2025-12-07T12:00:00-0500") (list :id "sat-pm-gen" :track "General" :start "2025-12-07T13:00:00-0500" :end "2025-12-07T17:00:00-0500") (list :id "sat-am-dev" :track "Development" :start "2025-12-07T10:00:00-0500" :end "2025-12-07T12:00:00-0500") (list :id "sat-pm-dev" :track "Development" :start "2025-12-07T13:00:00-0500" :end "2025-12-07T17:00:00-0500") (list :id "sun-am-gen" :track "General" :start "2025-12-08T09:00:00-0500" :end "2025-12-08T12:00:00-0500") (list :id "sun-pm-gen" :track "General" :start "2025-12-08T13:00:00-0500" :end "2025-12-08T17:00:00-0500")))
(defun emacsconf-filter-talks-by-time (start-time end-time info)
"Return talks that are between START-TIME and END-TIME (inclusive) in INFO."