Sacha Chua https://sachachua.com/blog/feed/ Emacs, sketches, and life Mon, 27 Nov 2023 15:03:45 GMT en-US daily 1 11ty 2023-11-27 Emacs news https://sachachua.com/blog/2023/11/2023-11-27-emacs-news/ Mon, 27 Nov 2023 15:03:45 GMT emacs emacs-news https://sachachua.com/blog/2023/11/2023-11-27-emacs-news/
  • EmacsConf 2023 is next weekend (Dec 2-3)!
  • Help wanted:
  • Upcoming events:
    • Emacs Berlin (virtual, in English) https://emacs-berlin.org/ Wed Nov 29 0930 America/Vancouver - 1130 America/Chicago - 1230 America/Toronto - 1730 Etc/GMT - 1830 Europe/Berlin - 2300 Asia/Kolkata – Thu Nov 30 0130 Asia/Singapore
    • M-x Research (contact them for password): TBA https://m-x-research.github.io/ Fri Dec 1 0800 America/Vancouver - 1000 America/Chicago - 1100 America/Toronto - 1600 Etc/GMT - 1700 Europe/Berlin - 2130 Asia/Kolkata – Sat Dec 2 0000 Asia/Singapore
    • Mastering Emacs book club https://susam.net/maze/meet/mastering-emacs/ Fri Dec 1 1200 America/Vancouver - 1400 America/Chicago - 1500 America/Toronto - 2000 Etc/GMT - 2100 Europe/Berlin – Sat Dec 2 0130 Asia/Kolkata - 0400 Asia/Singapore
    • Mastering Emacs book club https://susam.net/maze/meet/mastering-emacs/ Sat Dec 2 0200 America/Vancouver - 0400 America/Chicago - 0500 America/Toronto - 1000 Etc/GMT - 1100 Europe/Berlin - 1530 Asia/Kolkata - 1800 Asia/Singapore
    • EmacsConf 2023 https://emacsconf.org/2023 Sat Dec 2 0600 America/Vancouver - 0800 America/Chicago - 0900 America/Toronto - 1400 Etc/GMT - 1500 Europe/Berlin - 1930 Asia/Kolkata - 2200 Asia/Singapore
    • Mastering Emacs book club https://susam.net/maze/meet/mastering-emacs/ Sun Dec 3 0200 America/Vancouver - 0400 America/Chicago - 0500 America/Toronto - 1000 Etc/GMT - 1100 Europe/Berlin - 1530 Asia/Kolkata - 1800 Asia/Singapore
    • EmacsConf 2023 https://emacsconf.org/2023 Sun Dec 3 0600 America/Vancouver - 0800 America/Chicago - 0900 America/Toronto - 1400 Etc/GMT - 1500 Europe/Berlin - 1930 Asia/Kolkata - 2200 Asia/Singapore
    • Emacs Paris: S: Emacs workshop in Paris (Octo) https://emacs-doctor.com/ Tue Dec 5 1830 Europe/Paris
    • EmacsATX: Eshell Insights https://www.meetup.com/emacsatx/events/295467667/ Wed Dec 6 1630 America/Vancouver - 1830 America/Chicago - 1930 America/Toronto – Thu Dec 7 0030 Etc/GMT - 0130 Europe/Berlin - 0600 Asia/Kolkata - 0830 Asia/Singapore
    • Emacs.si (in person): Emacs.si meetup #13 2023 (v #živo) https://dogodki.kompot.si/events/11bee38f-c56c-4417-a4c5-0ab81e90ba3f Thu Dec 7 1900 CET
    • Atelier Emacs Montpellier (in person) https://lebib.org/date/atelier-emacs Fri Dec 8 1800 Europe/Paris
    • Emacs.si: Emacs.si meetup #14 2023 (#online) https://dogodki.kompot.si/events/711108f9-1286-4d29-98f3-5043ce2d9ae6 Fri Dec 8 1100 America/Vancouver - 1300 America/Chicago - 1400 America/Toronto - 1900 Etc/GMT - 2000 Europe/Berlin – Sat Dec 9 0030 Asia/Kolkata - 0300 Asia/Singapore
  • Emacs configuration:
  • Emacs Lisp:
  • Appearance:
  • Navigation:
  • Dired:
  • Org Mode:
  • Completion:
  • Coding:
  • Math:
  • Shells:
  • Mail, news, and chat:
  • Multimedia:
  • AI:
  • Community:
  • Other:
  • Emacs development:
  • New packages:
  • Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, r/planetemacs, Hacker News, lobste.rs, kbin, programming.dev, lemmy, communick.news, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!

    ]]>
    2023-11-20 Emacs news https://sachachua.com/blog/2023/11/2023-11-20-emacs-news/ Mon, 20 Nov 2023 16:24:40 GMT emacs emacs-news https://sachachua.com/blog/2023/11/2023-11-20-emacs-news/
  • Upcoming events:
    • Mastering Emacs book club https://susam.net/maze/meet/mastering-emacs/ Fri Nov 24 1200 America/Vancouver - 1400 America/Chicago - 1500 America/Toronto - 2000 Etc/GMT - 2100 Europe/Berlin – Sat Nov 25 0130 Asia/Kolkata - 0400 Asia/Singapore
    • Emacs APAC (virtual) https://emacs-apac.gitlab.io/ Sat Nov 25 0030 America/Vancouver - 0230 America/Chicago - 0330 America/Toronto - 0830 Etc/GMT - 0930 Europe/Berlin - 1400 Asia/Kolkata - 1630 Asia/Singapore
    • Mastering Emacs book club https://susam.net/maze/meet/mastering-emacs/ Sat Nov 25 0200 America/Vancouver - 0400 America/Chicago - 0500 America/Toronto - 1000 Etc/GMT - 1100 Europe/Berlin - 1530 Asia/Kolkata - 1800 Asia/Singapore
    • Mastering Emacs book club https://susam.net/maze/meet/mastering-emacs/ Sun Nov 26 0200 America/Vancouver - 0400 America/Chicago - 0500 America/Toronto - 1000 Etc/GMT - 1100 Europe/Berlin - 1530 Asia/Kolkata - 1800 Asia/Singapore
    • Tbilisi Emacs Meetup (in person) https://akater.gitlab.io/emacs-tbilisi-meetup-page - 15:30 Georgia Time
    • Emacs Berlin (virtual, in English) https://emacs-berlin.org/ Wed Nov 29 0930 America/Vancouver - 1130 America/Chicago - 1230 America/Toronto - 1730 Etc/GMT - 1830 Europe/Berlin - 2300 Asia/Kolkata – Thu Nov 30 0130 Asia/Singapore
    • M-x Research (contact them for password): TBA https://m-x-research.github.io/ Fri Dec 1 0800 America/Vancouver - 1000 America/Chicago - 1100 America/Toronto - 1600 Etc/GMT - 1700 Europe/Berlin - 2130 Asia/Kolkata – Sat Dec 2 0000 Asia/Singapore
  • Emacs configuration:
  • Emacs Lisp:
  • Appearance:
  • Navigation:
  • Dired:
  • Writing:
  • Org Mode:
  • Denote:
  • Completion:
  • Coding:
  • Shells:
  • Community:
  • Other:
  • Emacs development:
  • New packages:
    • easky: Control the Eask command-line interface (MELPA)
    • llvm-ts-mode: LLVM major mode using tree-sitter (MELPA)
    • ruff-format: Ruff format Python source (MELPA)
    • sequential-yank: Minor mode to copy and paste strings sequentially (MELPA)
    • tab-bar-notch: Adjust tab-bar height for MacBook Pro notch (MELPA)
  • Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, r/planetemacs, Hacker News, lobste.rs, kbin, programming.dev, lemmy, communick.news, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!

    ]]>
    2023-11-13 Emacs news https://sachachua.com/blog/2023/11/2023-11-13-emacs-news/ Mon, 13 Nov 2023 14:30:35 GMT emacs emacs-news https://sachachua.com/blog/2023/11/2023-11-13-emacs-news/
  • Upcoming events:
    • EmacsSF (in person): hackerdojo.el https://www.meetup.com/emacs-sf/events/296985794/ Tue Nov 14 1830 America/Los_Angeles
    • M-x Research (contact them for password): TBA https://m-x-research.github.io/ Wed Nov 15 0800 America/Vancouver - 1000 America/Chicago - 1100 America/Toronto - 1600 Etc/GMT - 1700 Europe/Berlin - 2130 Asia/Kolkata – Thu Nov 16 0000 Asia/Singapore
    • Mastering Emacs book club https://susam.net/maze/meet/mastering-emacs/ Fri Nov 17 1200 America/Vancouver - 1400 America/Chicago - 1500 America/Toronto - 2000 Etc/GMT - 2100 Europe/Berlin – Sat Nov 18 0130 Asia/Kolkata - 0400 Asia/Singapore
    • Mastering Emacs book club https://susam.net/maze/meet/mastering-emacs/ Sat Nov 18 0200 America/Vancouver - 0400 America/Chicago - 0500 America/Toronto - 1000 Etc/GMT - 1100 Europe/Berlin - 1530 Asia/Kolkata - 1800 Asia/Singapore
    • Mastering Emacs book club https://susam.net/maze/meet/mastering-emacs/ Sun Nov 19 0200 America/Vancouver - 0400 America/Chicago - 0500 America/Toronto - 1000 Etc/GMT - 1100 Europe/Berlin - 1530 Asia/Kolkata - 1800 Asia/Singapore
    • Mastering Emacs book club https://susam.net/maze/meet/mastering-emacs/ Fri Nov 24 1200 America/Vancouver - 1400 America/Chicago - 1500 America/Toronto - 2000 Etc/GMT - 2100 Europe/Berlin – Sat Nov 25 0130 Asia/Kolkata - 0400 Asia/Singapore
    • Emacs APAC (virtual) https://emacs-apac.gitlab.io/ Sat Nov 25 0030 America/Vancouver - 0230 America/Chicago - 0330 America/Toronto - 0830 Etc/GMT - 0930 Europe/Berlin - 1400 Asia/Kolkata - 1630 Asia/Singapore, announcement
  • Emacs configuration:
  • Emacs Lisp:
  • Appearance:
  • Navigation:
  • Hyperbole:
  • Dired:
  • Org Mode:
  • Denote:
  • Completion:
  • Coding:
  • Shells:
  • Web:
  • Fun:
  • AI:
  • Community:
  • Other:
  • Emacs development:
  • New packages:
    • mark-yank: Set region to the last yank (MELPA)
    • jack-ts-mode: Major mode for jack buffers using tree-sitter (MELPA)
    • gemtext-mode: Major mode for Gemtext-formatted text (MELPA)
    • drepl: REPL protocol for the dumb terminal (GNU ELPA)
  • Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, r/planetemacs, Hacker News, lobste.rs, kbin, programming.dev, lemmy, communick.news, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!

    ]]>
    EmacsConf 2023 status update: stuff is happening! https://sachachua.com/blog/2023/11/emacsconf-2023-status-update-stuff-is-happening/ Tue, 07 Nov 2023 00:28:10 GMT emacs emacsconf https://sachachua.com/blog/2023/11/emacsconf-2023-status-update-stuff-is-happening/ EmacsConf 2023 is less than a month away. Speakers have been uploading videos, captioning volunteers have been editing away, and I thiiiiink I've gotten most of the infrastructure dusted off. Exciting!

    Here's where we are with regard to talk status:

    Graphical view of the schedule Schedule for Saturday Saturday 9:00- 9:10 Saturday opening remarks sat-open 9:10- 9:20 An Org-Mode based text adventure game for learning the basics of Emacs, inside Emacs, written in Emacs Lisp adventure 9:30- 9:50 Authoring and presenting university courses with Emacs and a full libre software stack uni 10:05-10:25 Teaching computer and data science with literate programming tools teaching 10:40-10:50 Who needs Excel? Managing your students qualifications with org-table table 11:05-11:15 Taming things with Org Mode taming 11:30-11:50 one.el: the static site generator for Emacs Lisp Programmers one 1:00- 1:10 Emacs turbo-charges my writing writing 1:25- 1:35 Why Nabokov would use Org-Mode if he were writing today nabokov 1:50- 2:10 Collaborative data processing and documenting using org-babel collab 2:20- 2:40 How I play TTRPGs in Emacs solo 2:55- 3:15 Org-Mode workflow: informal reference tracking ref 3:25- 3:35 (Un)entangling projects and repos unentangling 3:45- 3:55 Emacs development updates devel 4:10- 4:50 Emacs core development: how it works core 5:05- 5:15 Saturday closing remarks sat-close 10:00-10:10 MatplotLLM, iterative natural language data visualization in org-babel matplotllm 10:20-10:40 Improving access to AI-assisted literate programming with voice control voice 10:55-11:15 LLM clients in Emacs, functionality and standardization llm 1:00- 1:20 Improving compiler diagnostics with Overlays overlay 1:35- 1:45 Editor Integrated REPL Driven Development for all languages eval 2:00- 2:40 REPLs in strange places: Lua, LaTeX, LPeg, LPegRex, TikZ repl 2:50- 3:30 Literate Documentation with Emacs and Org Mode doc 3:45- 4:05 EmacsConf.org: How we use Org Mode and TRAMP to organize and run a multi-track conference emacsconf 9 AM 10 AM 11 AM 12 PM 1 PM 2 PM 3 PM 4 PM 5 PM Schedule for Sunday Sunday 9:00- 9:05 Sunday opening remarks sun-open 9:05- 9:25 Top 10 ways Hyperbole amps up Emacs hyperamp 9:40-10:00 Using Koutline for stream of thought journaling koutline 10:10-10:20 Parallel Text Replacement: Does P = NP? parallel 10:35-10:45 Eat and Eat powered Eshell, fast featureful terminal inside Emacs eat 11:00-11:20 The browser in a buffer poltys 11:35-11:55 Speedcubing in Emacs cubing 1:00- 1:40 Emacs MultiMedia System (EMMS) emms 1:55- 2:25 Programming at 200 wpm steno 2:35- 2:45 Mentoring VS-Coders as an Emacsian (or How to show not tell people about the wonders of Emacs) mentor 3:10- 3:50 Emacs saves the Web web 4:05- 4:25 Sharing Emacs is Caring Emacs: Emacs education and why I embraced video sharing 4:40- 4:50 Sunday closing remarks sun-close 10:00-10:20 Bringing joy to Scheme programming scheme 10:35-10:55 GNU Emacs: A World of Possibilities world 11:10-11:20 A modern Emacs look-and-feel without pain flat 11:35-11:55 The Emacsen family, the design of an Emacs and the importance of Lisp emacsen 1:00- 1:20 emacs-gc-stats: Does garbage collection actually slow down Emacs? gc 1:35- 2:15 hyperdrive.el: Peer-to-peer filesystem in Emacs hyperdrive 2:30- 2:40 Writing a language server in OCaml for Emacs, fun, and profit lspocaml 2:55- 3:15 What I learned by writing test cases for GNU Hyperbole test 3:30- 4:10 Windows into Freedom windows 9 AM 10 AM 11 AM 12 PM 1 PM 2 PM 3 PM 4 PM 5 PM

    Waiting for 26 talks (~550 minutes) out of 42 total. Talks received so far:

    • TO_ASSIGN (waiting for captioning volunteers) - 8 talk(s), 150 minutes: adventure (05:58), matplotllm (09:34), teaching (19:27), nabokov (09:51), collab (19:16), doc (42:45), scheme (21:01), emacsen (18:28)
    • TO_CAPTION - 2 talk(s), 21 minutes: eval (09:35), mentor (10:44)
    • TO_STREAM - 6 talk(s), 124 minutes: llm (20:26), writing (08:53), ref (15:04), emacsconf (15:05), world (22:20), emms (38:38)

    Speakers have been really nice about keeping in touch, so I'm not too stressed about gaps in the schedule. Captioning volunteers have been chugging through the talks and OpenAI Whisper's gotten a bit better at spelling things, so that's terrific too. It's so exciting!

    zaeph and bandali will probably host the general track and the development track respectively. They've done it for a number of years now, so it'll probably be fine even if we don't have a dry run all together since they've got limited availability. (And we can take on new volunteers if people want to help read questions!)

    My stress level is pretty manageable at this point. I can even spend evenings playing video games with the kiddo and weekends going on little bike adventures, so that's awesome. I'm still a little worried about tech hiccups, but we'll probably be able to figure things out.

    Next steps are:

    • keep processing videos and captions
    • make the intro videos available so that speakers can correct my pronunciation of their names
    • smoothen out and document the process for last-minute submissions
    • test everything again

    It's happening!

    ]]>
    2023-11-06 Emacs news https://sachachua.com/blog/2023/11/2023-11-06-emacs-news/ Mon, 06 Nov 2023 14:13:58 GMT emacs emacs-news https://sachachua.com/blog/2023/11/2023-11-06-emacs-news/
  • Upcoming events:
  • Emacs configuration:
  • Emacs Lisp:
  • Appearance:
  • Writing:
  • Org Mode:
  • Coding:
  • Shells:
  • Evil mode:
  • Community:
  • Other:
  • Emacs development:
  • New packages:
    • bufferlo: Manage frame/tab-local buffer lists (GNU ELPA)
    • daml-lsp: LSP client definition for daml (MELPA)
    • daml-mode: Major mode for daml (MELPA)
    • decor: Modify visual decorations (X11) (MELPA)
    • flymake-codespell: Flymake backend for codespell (GNU ELPA)
    • plz-see: Interactive HTTP client (GNU ELPA)
    • smlfmt: Format SML source code using the "smlfmt" program (MELPA)
  • Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, r/planetemacs, Hacker News, communick.news, lobste.rs, kbin, programming.dev, lemmy, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!

    ]]>
    2023-10-30 Emacs news https://sachachua.com/blog/2023/10/2023-10-30-emacs-news/ Mon, 30 Oct 2023 13:57:30 GMT emacs emacs-news https://sachachua.com/blog/2023/10/2023-10-30-emacs-news/
  • Upcoming events:
  • Emacs configuration:
  • Emacs Lisp:
  • Appearance:
  • Navigation:
  • Writing:
  • Org Mode:
  • Coding:
  • Shells:
  • Mail, news, and chat:
  • Multimedia:
  • Fun:
  • AI:
  • Community:
  • Other:
  • Emacs development:
  • New packages:
  • Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, r/planetemacs, Hacker News, communick.news, lobste.rs, kbin, programming.dev, lemmy, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!

    ]]>
    #EmacsConf backstage: coordinating captioning volunteers using a backstage area https://sachachua.com/blog/2023/10/emacsconf-backstage-coordinating-captioning-volunteers-using-a-backstage-area/ Sun, 29 Oct 2023 01:39:35 GMT emacs emacsconf https://sachachua.com/blog/2023/10/emacsconf-backstage-coordinating-captioning-volunteers-using-a-backstage-area/ 2023-10-28_21-32-21.png
    Figure 1: The backstage area

    One of the benefits of volunteering for or speaking at EmacsConf is that you get early access to the talks. We upload the files to a password-protected web server. It's nothing fancy, just a directory that has basic authentication and a static index.html generated by Emacs Lisp.

    I organize the talks by TODO status so that people can easily see which talks are ready to be captioned. Since captioning can take a little while, we reserve the talks we want to caption. That way, people don't accidentally work on a talk that someone else is already captioning. When people e-mail me to reserve a talk, I move the talk from TO_ASSIGN to TO_CAPTION and add their name in the :CAPTIONER: property.

    Publishing the backstage index is done by an Emacs Lisp function that smooshes the information together and then writes the files directly to the server using TRAMP.

    emacsconf-publish-backstage-index
    (defun emacsconf-publish-backstage-index (&optional filename)
      (interactive)
      (setq filename (or filename (expand-file-name "index.html" emacsconf-backstage-dir)))
      (let ((info (or emacsconf-schedule-draft (emacsconf-publish-prepare-for-display (emacsconf-get-talk-info))))
            (emacsconf-main-extensions (append emacsconf-main-extensions emacsconf-publish-backstage-extensions)))
        (with-temp-file filename
          (let* ((talks
                  (mapcar
                   (lambda (o) (append
                                (list :captions-edited t) o))
                   (seq-filter (lambda (o) (plist-get o :speakers))
                               (emacsconf-active-talks (emacsconf-filter-talks info)))))
                 (by-status (seq-group-by (lambda (o) (plist-get o :status)) talks))
                 (files (directory-files emacsconf-backstage-dir)))
            (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: 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
                 (mapc (lambda (node)
                         (dom-set-attribute
                          node 'href
                          (concat "#" (dom-attr node 'data-slug))))
                       (dom-by-tag img 'a))
                 (svg-print img)
                 (buffer-string)))
             "</p>"
             (if (eq emacsconf-backstage-phase 'prerec)
                 (format "<p>Waiting for %d talks (~%d minutes) out of %d total</p>"
                         (length (assoc-default "WAITING_FOR_PREREC" by-status))
                         (emacsconf-sum :time (assoc-default "WAITING_FOR_PREREC" by-status))
                         (length talks))
               "")
             "<ul>"
             (mapconcat
              (lambda (status)
                (concat "<li>"
                        (if (string= status "TO_ASSIGN")
                            "TO_ASSIGN (waiting for volunteers)"
                          status)
                        ": "
                        (mapconcat (lambda (o) (format "<a href=\"#%s\">%s</a>"
                                                       (plist-get o :slug)
                                                       (plist-get o :slug)))
                                   (assoc-default status by-status)
                                   ", ")
                        "</li>"))
              (pcase emacsconf-backstage-phase
                ('prerec '("TO_PROCESS" "PROCESSING" "TO_ASSIGN" "TO_CAPTION" "TO_STREAM"))
                ('harvest '("TO_ARCHIVE" "TO_REVIEW_QA" "TO_INDEX_QA" "TO_CAPTION_QA")))
              "")
             "</ul>"
             (pcase emacsconf-backstage-phase
               ('prerec
                (concat
                 (emacsconf-publish-backstage-processing by-status files)
                 (emacsconf-publish-backstage-to-assign by-status files)
                 (emacsconf-publish-backstage-to-caption by-status files)
                 (emacsconf-publish-backstage-to-stream by-status files)))
               ('harvest
                (let ((stages
                       '(("TO_REVIEW_QA" .
                          "Please review the --bbb-webcams.webm file and/or the --bbb-webcams.vtt and tell us (emacsconf-submit@gnu.org) if a Q&amp;A session can be published or if it needs to be trimmed (lots of silence at the end of the recording, accidentally included sensitive information, etc.).")
                         ("TO_INDEX_QA" .
                          "Please review the --answers.webm and --answers.vtt files to make chapter markers so that people can jump to specific parts of the Q&amp;A session. The <a href=\"https://emacsconf.org/harvesting/\">harvesting page on the wiki</a> has some notes on the process. That way, it's easier for people to see the questions and
    answers without needing to listen to everything again. You can see <a href=\"https://emacsconf.org/2022/talks/asmblox\">asmblox</a> for an example of the Q&amp;A chapter markers.")
                         ("TO_CAPTION_QA" .
                          "Please edit the --answers.vtt for the Q&amp;A talk you're interested in, correcting misrecognized words and cleaning it up so that it's nice to use as closed captions. All talks should now have large-model VTTs to make it easier to edit."))))
                  (mapconcat
                   (lambda (stage)
                     (let ((status (car stage)))
                       (format
                        "<h1>%s: %d talk(s) (%d minutes)</h1>%s<ol class=\"videos\">%s</ol>"
                        status
                        (length (assoc-default status by-status))
                        (emacsconf-sum :video-time (assoc-default status by-status))
                        (cdr stage)
                        (mapconcat
                         (lambda (f)
                           (format  "<li><a name=\"%s\"></a><strong><a href=\"%s%s\">%s</a></strong><br />%s (id:%s)<br />%s</li>"
                                    (plist-get f :slug)
                                    emacsconf-base-url
                                    (plist-get f :url)
                                    (plist-get f :title)
                                    (plist-get f :speakers-with-pronouns)
                                    (plist-get f :slug)
                                    (emacsconf-publish-index-card
                                     (append (list
                                              :video-note
                                              (unless (file-exists-p (expand-file-name (concat (plist-get f :file-prefix) "--bbb-webcams.webm") emacsconf-cache-dir))
                                                "<div>No Q&A video for this talk</div>")
                                              :video-file
                                              (cond
                                               ((file-exists-p (expand-file-name (concat (plist-get f :file-prefix) "--answers.webm") emacsconf-cache-dir))
                                                (concat (plist-get f :file-prefix) "--answers.webm"))
                                               ((file-exists-p (expand-file-name (concat (plist-get f :file-prefix) "--bbb-webcams.webm") emacsconf-cache-dir))
                                                (concat (plist-get f :file-prefix) "--bbb-webcams.webm"))
                                               (t t)) ;; omit video
                                              :video-id "-qanda"
                                              :extra
                                              (concat
                                               (emacsconf-surround "QA note: " (plist-get f :qa-note) "<br />")
                                               (format "Q&A archiving: <a href=\"%s-%s.txt\">IRC: %s-%s</a>"
                                                       (format-time-string "%Y-%m-%d" (plist-get f :start-time))
                                                       (plist-get (emacsconf-get-track f) :channel)
                                                       (format-time-string "%Y-%m-%d" (plist-get f :start-time))
                                                       (plist-get (emacsconf-get-track f) :channel))
                                               (emacsconf-surround ", <a href=\""
                                                                   (if (file-exists-p (expand-file-name (concat (plist-get f :file-prefix) "--pad.txt")
                                                                                                        emacsconf-cache-dir))
                                                                       (concat (plist-get f :file-prefix) "--pad.txt"))
                                                                   "\">Etherpad (Markdown)</a>" "")
                                               (emacsconf-surround ", <a href=\"" (plist-get f :bbb-playback) "\">BBB playback</a>" "")
                                               (emacsconf-surround ", <a href=\""
                                                                   (if (file-exists-p (expand-file-name (concat (plist-get f :file-prefix) "--bbb.txt")
                                                                                                        emacsconf-cache-dir))
                                                                       (concat (plist-get f :file-prefix) "--bbb.txt"))
                                                                   "\">BBB text chat</a>" "")
                                               (emacsconf-surround ", <a href=\""
                                                                   (if (file-exists-p (expand-file-name (concat (plist-get f :file-prefix) "--bbb-webcams.opus")
                                                                                                        emacsconf-cache-dir))
                                                                       (concat (plist-get f :file-prefix) "--bbb-webcams.opus"))
                                                                   "\">BBB audio only</a>" ""))
                                              :files (emacsconf-publish-talk-files f files))
                                             f)
                                     emacsconf-main-extensions)))
                         (assoc-default status by-status) "\n"))))
                   stages
                   "\n"))))
             (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>")))))
    

    For example, the section for talks that are waiting for volunteers is handled by the function emacsconf-publish-backstage-to-assign.

    emacsconf-publish-backstage-to-assign
    (defun emacsconf-publish-backstage-to-assign (by-status files)
      (let ((list (assoc-default "TO_ASSIGN" by-status)))
        (format "<h1>%s talk(s) to be captioned, waiting for volunteers (%d minutes)</h1><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. If you're writing them 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><ul class=\"videos\">%s</ul>"
                (length list)
                (emacsconf-sum :video-time list)
                (mapconcat
                 (lambda (f)
                   (setq f (append
                            f
                            (list :extra
                                  (if (plist-get f :caption-note) (concat "<div class=\"caption-note\">" (plist-get f :caption-note) "</div>") "")
                                  :files
                                  (emacsconf-publish-talk-files f files))))
                   (format  "<li><a name=\"%s\"></a><strong><a href=\"%s\">%s</a></strong><br />%s (id:%s)<br />%s</li>"
                            (plist-get f :slug)
                            (plist-get f :absolute-url)
                            (plist-get f :title)
                            (plist-get f :speakers)
                            (plist-get f :slug)
                            (emacsconf-publish-index-card f)))
                 list
                 "\n"))))
    

    Each talk has a little card that includes its video and links to files.

    emacsconf-publish-index-card: Format an HTML card for TALK, linking the files in EXTENSIONS.
    (defun emacsconf-publish-index-card (talk &optional extensions)
      "Format an HTML card for TALK, linking the files in EXTENSIONS."
      (let* ((file-prefix (plist-get talk :file-prefix))
             (video-file (plist-get talk :video-file))
             (video (and file-prefix
                         (emacsconf-publish-index-card-video
                          (or (plist-get talk :video-id)
                              (concat (plist-get talk :slug) "-mainVideo"))
                          video-file talk extensions))))
        ;; Add extra information to the talk
        (setq talk
              (append
               talk
               (list
                :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) "")
                :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>")))
    

    So it's all built on templates and a little bit of code to make the appropriate lists. You can find this code in emacsconf-publish.el.

    ]]>
    #EmacsConf backstage: automatically updating talk status from the crontab https://sachachua.com/blog/2023/10/emacsconf-backstage-automatically-updating-talk-status-from-the-crontab/ Thu, 26 Oct 2023 19:00:09 GMT emacs emacsconf org https://sachachua.com/blog/2023/10/emacsconf-backstage-automatically-updating-talk-status-from-the-crontab/ Now that I've figured out how to automatically play EmacsConf talks with crontab, I want to update our approach to using TRAMP and timers to run two tracks semi-automatically.

    When a talk starts playing, I would like to:

    • announce it on IRC so that the talk details split up the chat log and make it easier to read
    • publish the media files to https://media.emacsconf.org/2023 (currently password-protected while testing) so that people can view them
    • update the talk wiki page with the media files and the captions
    • update the Youtube and Toobnix videos so that they're public: this is manual for the moment, since I haven't put time into automating it yet

    I have code for most of this, and we've done it successfully for the past couple of years. I just need to refamiliarize myself with how to do it and how to set it up for testing during the dry run, and modify it to work with the new crontab-based system.

    Setting things up

    Last year, I set up the server with the ability to act as the controller, so that it wasn't limited to my laptop. The organizers notebook says I put it in the orga@ user's account, and I probably ran it under a screen session. I've submitted a talk for this year's conference, so I can use a test video for that one. First, I need to update the Ansible configuration for publishing and editing:

    ansible-playbook -i inventory.yml prod-playbook.yml --tags publish,edit
    

    I needed to add a version attribute to the git repo checkout in the Ansible playbook, since we'd switched from master to main. I also needed to set emacs_version to 29.1 since I started using seq-keep in my Emacs Lisp functions. For testing, I set emacsconf-publishing-phase to conference.

    Act on TODO state changes

    org-after-todo-state-change-hook makes it easy to automatically run functions when the TODO state changes. I add this hook that runs a list of functions and passes the talk information so that I don't have to parse the talk info in each function.

    emacsconf-org-after-todo-state-change: Run all the hooks in ‘emacsconf-todo-hooks’.
    (defun emacsconf-org-after-todo-state-change ()
      "Run all the hooks in `emacsconf-todo-hooks'.
    If an `emacsconf-todo-hooks' entry is a list, run it only for the
    tracks with the ID in the cdr of that list."
      (let* ((talk (emacsconf-get-talk-info-for-subtree))
             (track (emacsconf-get-track (plist-get talk :track))))
        (mapc
         (lambda (hook-entry)
           (cond
            ((symbolp hook-entry) (funcall hook-entry talk))
            ((member (plist-get track :id) (cdr hook-entry))
             (funcall (car hook-entry) talk))))
         emacsconf-todo-hooks)))
    

    This can be enabled and disabled with the following functions.

    emacsconf-add-org-after-todo-state-change-hook: Add FUNC to ‘org-after-todo-stage-change-hook’.
    (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)
        (add-hook 'org-after-todo-state-change-hook #'emacsconf-org-after-todo-state-change nil t)))
    

    emacsconf-remove-org-after-todo-state-change-hook: Remove FUNC from ‘org-after-todo-stage-change-hook’.
    (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)
        (remove-hook 'org-after-todo-state-change-hook
                     #'emacsconf-org-after-todo-state-change  t)))
    

    Announce on IRC

    This is still much the same as last year.

    emacsconf-erc-announce-on-change: Announce talk.
    (defun emacsconf-erc-announce-on-change (talk)
      "Announce talk."
      (let ((func
             (pcase org-state
               ("PLAYING" #'erc-cmd-NOWPLAYING)
               ("CLOSED_Q" #'erc-cmd-NOWCLOSEDQ)
               ("OPEN_Q" #'erc-cmd-NOWOPENQ)
               ("UNSTREAMED_Q" #'erc-cmd-NOWUNSTREAMEDQ)
               ("TO_ARCHIVE" #'erc-cmd-NOWDONE))))
        (when func
          (funcall func talk))))
    

    Here's a sample command that announces that the talk is now playing.

    erc-cmd-NOWPLAYING: Set the channel topics to announce TALK.
    (defun erc-cmd-NOWPLAYING (talk)
      "Set the channel topics to announce TALK."
      (interactive (list (emacsconf-complete-talk-info)))
      (when (stringp talk)
        (setq talk (or (emacsconf-find-talk-info talk) (error "Could not find talk %s" talk))))
      ;; Announce it in the track's channel
      (if (emacsconf-erc-recently-announced (format "---- %s:" (plist-get talk :slug)))
          (message "Recently announced, skipping")
        (when (plist-get talk :track)
          (emacsconf-erc-with-channels (list (concat "#" (plist-get talk :channel)))
            (erc-cmd-TOPIC
             (format
              "%s: %s (%s) pad: %s Q&A: %s | %s"
              (plist-get talk :slug)
              (plist-get talk :title)
              (plist-get talk :speakers)
              (plist-get talk :pad-url)
              (plist-get talk :qa-info)
              (car (assoc-default
                    (concat "#" (plist-get talk :channel))
                    emacsconf-topic-templates))))
            (erc-send-message (format "---- %s: %s - %s ----"
                                      (plist-get talk :slug)
                                      (plist-get talk :title)
                                      (plist-get talk :speakers-with-pronouns)))
            (erc-send-message
             (concat "Add your notes/questions to the pad: " (plist-get talk :pad-url)))
            (cond
             ((string-match "live" (or (plist-get talk :q-and-a) ""))
              (erc-send-message (concat "Live Q&A: " (plist-get talk :bbb-redirect))))
             ((plist-get talk :irc)
              (erc-send-message (format "or discuss the talk on IRC (nick: %s)"
                                        (plist-get talk :irc)))))))
        ;; Short announcement in #emacsconf
        (emacsconf-erc-with-channels (list emacsconf-erc-hallway emacsconf-erc-org)
          (erc-send-message (format "-- %s track: %s: %s (watch: %s, pad: %s, channel: #%s)"
                                    (plist-get talk :track)
                                    (plist-get talk :slug)
                                    (plist-get talk :title)
                                    (plist-get talk :watch-url)
                                    (plist-get talk :pad-url)
                                    (plist-get talk :channel))))))
    

    Because the commands change the topic, I need to op the user in all the Emacsconf channels.

    erc-cmd-OPALL
    (defun erc-cmd-OPALL (&optional nick)
      (emacsconf-erc-with-channels (mapcar 'car emacsconf-topic-templates)
        (if nick
            (erc-cmd-OP nick)
          (erc-cmd-OPME))))
    

    This code is in emacsconf-erc.el.

    Publish media files

    We used to scramble to upload all the videos in the days or weeks after the conference, since the presentations were live. Since we switched to encouraging speakers to upload videos before the conference, we've been able to release the videos pretty much as soon as the talk starts playing. This code in emacsconf-publish.el takes care of copying the files from the backstage to the public media directory, republishing the index, and republishing the playlist. That way, people who come in late or who want to refer to the video can easily get the full video right away.

    emacsconf-publish-media-files-on-change: Publish the files and update the index.
    (defun emacsconf-publish-media-files-on-change (talk)
      "Publish the files and update the index."
      (interactive (list (emacsconf-complete-talk-info)))
      (let ((org-state (if (boundp 'org-state) org-state (plist-get talk :status))))
        (if (plist-get talk :public)
            ;; Copy main extension files from backstage to public
            (let ((files (directory-files emacsconf-backstage-dir nil
                                          (concat "^"
                                                  (regexp-quote (plist-get talk :file-prefix))
                                                  (regexp-opt emacsconf-main-extensions)))))
              (mapc (lambda (file)
                      (when (and
                             (not (file-exists-p (expand-file-name file emacsconf-public-media-directory)))
                             (or (not (string-match "--main.vtt$" file))
                                 (plist-get talk :captions-edited)))
                        (copy-file (expand-file-name file emacsconf-backstage-dir)
                                   (expand-file-name file emacsconf-public-media-directory) t)))
                    files))
          ;; Remove files from public
          (let ((files (directory-files emacsconf-public-media-directory nil
                                        (concat "^"
                                                (regexp-quote (plist-get talk :file-prefix)
                                                              )))))
            (mapc (lambda (file)
                    (delete-file (expand-file-name file emacsconf-public-media-directory)))
                  files)))
        (emacsconf-publish-public-index)
        (emacsconf-publish-playlist
         (expand-file-name "index.m3u" emacsconf-public-media-directory)
         (concat emacsconf-name " " emacsconf-year)
         (emacsconf-public-talks (emacsconf-get-talk-info)))))
    

    The :public property is automatically added by this function based on the TODO status or the time:

    emacsconf-add-talk-status: Add status label and public info.
    (defun emacsconf-add-talk-status (o)
      "Add status label and public info."
      (plist-put o :status-label
                 (or (assoc-default (plist-get o :status)
                                    emacsconf-status-types 'string= "")
                     (plist-get o :status)))
      (when (or
             (member (plist-get o :status)
                     (split-string "PLAYING CLOSED_Q OPEN_Q UNSTREAMED_Q TO_ARCHIVE TO_EXTRACT TO_FOLLOW_UP DONE"))
             (time-less-p (plist-get o :start-time)
                          (current-time)))
        (plist-put o :public t))
      o)
    

    Update the wiki page

    This function updates the schedule page and the page for the talk. It's also in emacsconf-publish.el.

    emacsconf-publish-update-talk: Publish the schedule page and the page for this talk.
    (defun emacsconf-publish-update-talk (talk)
      "Publish the schedule page and the page for this talk."
      (interactive (list (emacsconf-complete-talk-info)))
      (when (stringp talk) (setq talk (emacsconf-resolve-talk talk)))
      (when (functionp 'emacsconf-upcoming-insert-or-update)
        (emacsconf-upcoming-insert-or-update))
      (emacsconf-publish-with-wiki-change
        (let ((info (emacsconf-publish-prepare-for-display (emacsconf-get-talk-info))))
          (emacsconf-publish-before-page talk info)
          (emacsconf-publish-after-page talk info)
          (emacsconf-publish-schedule info))))
    

    It uses the publishing functions I described in this post on adding talks to the wiki.

    This macro commits changes when emacsconf-publish-autocommit-wiki is t, so I need to set that also.

    emacsconf-publish-with-wiki-change
    (defmacro emacsconf-publish-with-wiki-change (&rest body)
      (declare (indent 0) (debug t))
      `(progn
         ,@body
         (emacsconf-publish-commit-and-push-wiki-maybe
          ,emacsconf-publish-autocommit-wiki
          (and (stringp ,(car body)) ,(car body)))))
    

    Make the videos public on YouTube and Toobnix

    This is low-priority, but it might be nice to figure out. The easiest way is probably to use open the Youtube/Toobnix URLs on my computer and then use either Tampermonkey or Spookfox to set the talk to public. Someday!

    Update the talk status on the server

    Last year, I experimented with having the shell scripts automatically update the status of the talk from TO_STREAM to PLAYING and from PLAYING to CLOSED_Q. Since I've moved the talk-running into track-specific crontabs, now I need to sudo back to the orga user and set XDG_RUNTIME_DIR in order to use emacsclient. I can call this with sudo -u orga talk $slug $status in the roles/obs/templates/handle-session script.

    Here's the Ansible template for roles/prerec/templates/talk. It uses getent to look up the user ID.

    #!/bin/bash
    # 
    # How to use: talk slug from-status-regexp to-status
    # or talk slug to-status
    
    SLUG="$1"
    FROM_STATUS="$2"
    TO_STATUS="$3"
    XDG_RUNTIME_DIR=/run/user/
    
    if [ "x$TO_STATUS" == "x" ]; then
        FROM_STATUS=.
        TO_STATUS="$2"
    fi
    cd 
    #echo "Pulling conf.org..."
    #git pull
    echo "Updating status..."
    XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR emacsclient --eval "(emacsconf-with-todo-hooks (emacsconf-update-talk-status \"$SLUG\" \"$FROM_STATUS\" \"$TO_STATUS\"))" -a emacs
    #echo "Committing and pushing in the background"
    #git commit -m "Update task status for $SLUG from $FROM_STATUS to $TO_STATUS" conf.org
    #git push &
    

    Testing notes

    Looks like everything works fine when I run it from the crontab: the talk status is updated, the media files are published, the wiki is updated, and the talks are announced on IRC. Backup plan A is to manually control the talk status using Emacs on the server. Backup plan B is to control the talk status using Emacs on my laptop. Backup plan C is to call the individual functions instead of relying on the todo state change functions. I think it'll all work out, although I'll probably want to do another dry run at some point to make sure. Slowly getting there…

    ]]>
    #EmacsConf backstage: autopilot with crontab https://sachachua.com/blog/2023/10/emacsconf-backstage-autopilot-with-crontab/ Tue, 24 Oct 2023 14:43:35 GMT emacs emacsconf https://sachachua.com/blog/2023/10/emacsconf-backstage-autopilot-with-crontab/

    [2023-10-26 Thu]: updated handle-session and added talk

    I figured out multi-track streaming so close to EmacsConf 2022 that there wasn't enough time to get other volunteers used to working with the setup, especially since I was still scrambling to figure out more infrastructure as the conference approached. We decided I'd run both streams myself, which meant I needed to make things as automatic as possible so that I wouldn't go crazy. I wanted a lot of things to happen automatically: playing recorded intros and videos, browsing to the right URLs depending on the type of Q&A, publishing updates to the wiki, and so on.

    I used timers and TODO state changes to execute commands via TRAMP, which was pretty cool for the most part. But it turned out TRAMP doesn't like being called when it's already running, like when it's being called from two timers going off at the same time. It gives a "Forbidden reentrant call of TRAMP". We found a couple of quick workarounds: I could reschedule the talks to be a minute apart, or I could cancel the conflicting timer and just start them with the shell scripts.

    Last year, we had a shell script that played the intro and the main talk, and other scripts to handle the Q&A by opening BigBlueButton, Etherpad, or the IRC channel. Much of the logic was in Emacs Lisp because it was easy to write it that way. For this year, I wanted to write a script that handled the intro, video, and Q&A portions. This is now in roles/obs/templates/handle-session.

    handle-session
    #!/bin/bash
    # 
    #
    # Handle the intro/talk/Q&A for a session
    # Usage: handle-session $SLUG
    
    YEAR=""
    BASE_DIR=""
    FIREFOX_NAME=firefox-esr
    SLUG=$1
    
    # Kill background music if playing
    if screen -list | grep -q background; then
        screen -S background -X quit
    fi
    
    # Update the status
    sudo -u  talk $SLUG PLAYING &
    
    # Update the overlay
    overlay $SLUG
    
    # Play the intro if it exists. If it doesn't exist, switch to the intro slide and stop processing.
    
    if [[ -f $BASE_DIR/assets/intros/$SLUG.webm ]]; then
      killall -s TERM $FIREFOX_NAME
      mpv $BASE_DIR/assets/intros/$SLUG.webm
    else
      firefox --kiosk $BASE_DIR/assets/in-between/$SLUG.png
      exit 0
    fi
    
    # Play the video if it exists. If it doesn't exist, switch to the BBB room and stop processing.
    if [ "x$TEST_MODE" = "x" ]; then
      LIST=($BASE_DIR/assets/stream/--$SLUG*--main.webm)
    else
      LIST=($BASE_DIR/assets/test/--$SLUG*--main.webm)
    fi
    FILE="${LIST[0]}"
    if [ ! -f "$FILE" ]; then
        # Is there an original file?
        LIST=($BASE_DIR/assets/stream/--$SLUG*--original.{webm,mp4,mov})
        FILE="${LIST[0]}"
    fi
    
    if [[ -f $FILE ]]; then
      killall -s TERM $FIREFOX_NAME
      mpv $FILE
    else
      /usr/local/bin/bbb $SLUG
      exit 0
    fi
    
    sudo -u  talk $SLUG CLOSED_Q &
    
    # Open the appropriate Q&A URL
    QA=$(jq -r '.talks[] | select(.slug=="'$SLUG'")["qa-backstage-url"]' < $BASE_DIR/talks.json)
    QA_TYPE=$(jq -r '.talks[] | select(.slug=="'$SLUG'")["qa-type"]' < $BASE_DIR/talks.json)
    echo "QA_TYPE $QA_TYPE QA $QA"
    if [ "$QA_TYPE" = "live" ]; then
      /usr/local/bin/bbb $SLUG
    elif [ "$QA" != "null" ]; then
      /usr/local/bin/music &
      /usr/bin/firefox $QA
      # i3-msg 'layout splith'
    fi
    wait
    

    It builds on roles/obs/templates/bbb, roles/obs/templates/overlay, and roles/obs/templates/music. I also have a roles/prerec/templates/talk script that uses emacsclient to update the status of the talk.

    I wrote some Tampermonkey scripts to automate joining the web conference and the IRC channel.

    Now that we have a script that handles all the different things related to a session, it's easier to schedule the execution of that script. Instead of using Emacs timers and running into that problem with tramp, I want to try using cron. Cron is a standard UNIX and Linux tool for scheduling things to run at certain times. You make a plain text file in a particular format: minute, hour, day of month, month, day of week, and then the command, and then you tell cron to use that file with something like crontab your-file. Since it's plain text, we can generate it with Emacs Lisp and format-time-string, save with TRAMP, and install with ssh. Each track has its own user account for streaming, so each track can have its own file.

    emacsconf-stream-format-crontab: Return crontab entries for TALKS.
    (defun emacsconf-stream-format-crontab (track talks &optional test-mode)
      "Return crontab entries for TALKS.
    Use the display specified in TRACK.
    If TEST-MODE is non-nil, load the videos from the test directory."
      (concat
       (format
        "PATH=/usr/local/bin:/usr/bin
    MAILTO=\"\"
    XDG_RUNTIME_DIR=\"/run/user/%d\"
    " (plist-get track :uid))
       (mapconcat
        (lambda (talk)
          (format "%s /usr/bin/screen -dmS play-%s bash -c \"DISPLAY=%s TEST_MODE=%s /usr/local/bin/handle-session %s | tee -a ~/track.log\"\n"
                  ;; cron times are UTC
                  (format-time-string "%-M %-H %-d %m *" (plist-get talk :start-time))
                  (plist-get talk :slug)
                  (plist-get track :vnc-display)
                  (if test-mode "1" "")
                  (plist-get talk :slug)))
        (emacsconf-filter-talks talks))))
    

    emacsconf-stream-crontabs: Write the streaming users’ crontab files.
    (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 INFO is non-nil, use that as the schedule instead."
      (interactive)
      (let ((emacsconf-publishing-phase 'conference))
        (setq info (or info (emacsconf-publish-prepare-for-display (emacsconf-get-talk-info))))
        (dolist (track emacsconf-tracks)
          (let ((talks (seq-filter (lambda (talk)
                                     (string= (plist-get talk :track)
                                              (plist-get track :name)))
                                   info))
                (crontab (expand-file-name (concat (plist-get track :id) ".crontab")
                                           (concat (plist-get track :tramp) "~"))))
            (with-temp-file crontab
              (when (plist-get track :autopilot)
                (insert (emacsconf-stream-format-crontab track talks test-mode))))
            (emacsconf-stream-track-ssh track (concat "crontab ~/" (plist-get track :id) ".crontab"))))))
    

    I want to test the whole setup before the conference, of course. First, I needed test videos. This generates test videos and subtitles following our naming convention.

    emacsconf-stream-generate-test-videos
    (defun emacsconf-stream-generate-test-videos (&optional info)
      "Generate 1-minute test videos for INFO."
      (interactive)
      (setq info (or info (emacsconf-publish-prepare-for-display (emacsconf-get-talk-info))))
      (let* ((dir (expand-file-name "test" emacsconf-stream-asset-dir))
             (default-directory dir)
             (subed-default-subtitle-length 1000)
             (test-length 60))
        (unless (file-directory-p dir)
          (make-directory dir t))
        (shell-command
         (format "ffmpeg -y -f lavfi -i testsrc=duration=%d:size=1280x720:rate=10 -i background-music.opus -shortest %s "
                 test-length (expand-file-name "template.webm" dir)))
        (dolist (talk info)
          (with-temp-file (expand-file-name (concat (plist-get talk :file-prefix) "--main.vtt") dir)
            (subed-vtt-mode)
            (subed-auto-insert)
            (dotimes (i test-length)
              (subed-append-subtitle
               nil
               (* i 1000)
               (1- (* i 1000))
               (format "%s %02d %s"
                       (plist-get talk :slug)
                       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))))
    

    Then I needed to write a crontab based on a different schedule. This code sets up a series of test videos to start about a minute after I run the code, with the dev stream set up to start a minute after the gen stream.

    (let* ((offset-seconds 60)
           (start-time (time-add (current-time) offset-seconds))
           (emacsconf-schedule-validation-functions nil)
           (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-copy-previous-track))
           (schedule (emacsconf-schedule-prepare
                      (emacsconf-schedule-inflate-sexp
                       `(("GEN"
                          :start ,(format-time-string "%Y-%m-%d %H:%M" start-time)
                          :set-track "General")
                         (sat-open :time 1)
                         (uni :time 1) ; live Q&A
                         (adventure :time 1) ; pad Q&A
                         ("DEV"
                          :start
                          ,(format-time-string "%Y-%m-%d %H:%M" (time-add start-time 60))
                          :set-track "Development")
                         (repl :time 1) ; IRC
                         (matplotllm :time 1) ; pad
                         (voice :time 1) ; live
                         )))))
      (emacsconf-stream-crontabs t schedule))
    

    That generates gen.crontab and dev.crontab. This is what gen.crontab looks like for testing:

    PATH=/usr/local/bin:/usr/bin
    MAILTO=""
    XDG_RUNTIME_DIR="/run/user/2002"
    35 11 26 10 * /usr/bin/screen -dmS play-sat-open bash -c "DISPLAY=:5 TEST_MODE=1 /usr/local/bin/handle-session sat-open | tee -a ~/track.log"
    36 11 26 10 * /usr/bin/screen -dmS play-uni bash -c "DISPLAY=:5 TEST_MODE=1 /usr/local/bin/handle-session uni | tee -a ~/track.log"
    38 11 26 10 * /usr/bin/screen -dmS play-adventure bash -c "DISPLAY=:5 TEST_MODE=1 /usr/local/bin/handle-session adventure | tee -a ~/track.log"
    

    The result: for both tracks, the intro videos play, the test videos play, and web browsers go to the right places for the Q&A.

    In case I need to resume manual control:

    emacsconf-stream-cancel-crontab: Remove crontab for TRACK.
    (defun emacsconf-stream-cancel-crontab (track)
      "Remove crontab for TRACK."
      (interactive (list (emacsconf-complete-track)))
      (plist-put track :autopilot nil)
      (emacsconf-stream-track-ssh track "crontab -r"))
    

    emacsconf-stream-cancel-all-crontabs: Remove crontabs.
    (defun emacsconf-stream-cancel-all-crontabs ()
      "Remove crontabs."
      (interactive)
      (dolist (track emacsconf-tracks)
        (plist-put track :autopilot nil)
        (emacsconf-stream-track-ssh track "crontab -r")))
    

    Here are some things I learned along the way:

    • I needed to use timedatectl set-timezone America/Toronto to change the server's timezone to America/Toronto so that the crontab would run at the right time.

      In Ansible terms, that's:

      	- name: Set system timezone
      		tags: tz
      		community.general.timezone:
      			name: ""
      	- name: Restart cron
      		tags: tz
      		ansible.builtin.service:
      			name: cron
      			state: restarted
      
    • I also needed to specify the PATH so that I didn't need to add the absolute paths in all the other shell scripts, XDG_RUNTIME_DIR to get audio working, and DISPLAY so that windows showed up in the right place.

    I think this will let me run both tracks for EmacsConf with more ease and less frantic juggling. We'll see!

    ]]>
    2023-10-23 Emacs news https://sachachua.com/blog/2023/10/2023-10-23-emacs-news/ Mon, 23 Oct 2023 14:18:46 GMT emacs emacs-news https://sachachua.com/blog/2023/10/2023-10-23-emacs-news/
  • Emacs 29.1.90 pretest is available (Irreal)
  • Upcoming events:
  • Emacs configuration:
  • Emacs Lisp:
  • Navigation:
  • TRAMP:
  • Dired:
  • Writing:
  • Org Mode:
  • Completion:
  • Coding:
  • Mail, news, and chat:
  • EXWM:
  • AI:
  • Community:
  • Other:
  • Emacs development:
  • New packages:
    • conan: Generate flags for c++ using conan 2.0 (MELPA)
    • consult-todo: Search hl-todo keywords in consult (MELPA)
    • flycheck-eask: Eask support in Flycheck (MELPA)
    • third-time: Third Time: A Better Way to Work (MELPA)
    • tmux-mode: Major mode for tmux configuration (MELPA)
  • Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, r/planetemacs, Hacker News, communick.news, lobste.rs, kbin, programming.dev, lemmy, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!

    ]]>