diff options
Diffstat (limited to '')
-rw-r--r-- | emacsconf.el | 232 |
1 files changed, 183 insertions, 49 deletions
diff --git a/emacsconf.el b/emacsconf.el index 1a76a84..0aee6c5 100644 --- a/emacsconf.el +++ b/emacsconf.el @@ -34,20 +34,20 @@ "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-video-target-date "2025-10-31" "Target date for receiving talk videos from the speakers." :group 'emacsconf :type 'string) -(defcustom emacsconf-schedule-announcement-date "2023-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 'program +(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." @@ -125,7 +125,7 @@ (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-res-dir (format "/ssh:orga@res.emacsconf.org:/data/emacsconf/shared/%s" emacsconf-year)) (defvar emacsconf-media-extensions '("webm" "mkv" "mp4" "webm" "mov" "avi" "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") @@ -173,6 +173,7 @@ (interactive) (dired emacsconf-backstage-dir "-tl")) (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 +331,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 +450,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))) @@ -489,7 +553,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 +579,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 +615,7 @@ If INFO is specified, limit it to that list." (:prerec-info "PREREC_INFO") ;; Prep (:bbb-room "ROOM") + (:bbb-mod-code "BBB_MOD_CODE") ;; Processing (:file-prefix "FILE_PREFIX") (:video-file "VIDEO_FILE") @@ -574,7 +646,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 +757,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) @@ -879,10 +954,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 +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)) @@ -984,6 +1051,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,6 +1156,11 @@ 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))) @@ -1090,8 +1168,8 @@ The subheading should match `emacsconf-abstract-heading-regexp'." (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 @@ -1116,10 +1194,12 @@ The subheading should match `emacsconf-abstract-heading-regexp'." "a" #'emacsconf-announce "i e" #'emacsconf-insert-talk-email "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 +1442,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 @@ -1380,21 +1461,21 @@ If TIMEZONES is a string, split it by commas." :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-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 "offline"))) (defun emacsconf-get-track (name) "Get the track for NAME. @@ -1458,8 +1539,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-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." @@ -1531,6 +1611,39 @@ 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-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 +" + (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-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 +1651,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) @@ -1627,19 +1742,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 +1821,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 +1830,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 +1981,19 @@ 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))))) + (provide 'emacsconf) ;;; emacsconf.el ends here |