EmacsConf backstage: chapter markers
| emacsLong videos are easier to navigate with chapter markers, so I've been slowly adding chapter markers to the Q&A sessions for EmacsConf 2021. I wrote an IkiWiki template and some Javascript code so that adding chapter markers to the EmacsConf wiki should be just a matter of as adding something like this:
[[!template id="chapters" vidid="mainVideo" data=""" 00:00 Introduction 00:11 Upcoming Emacs 28 release 00:24 Org mode 9.5 00:57 Magit major release 01:18 Completion 01:51 Embark 02:12 tree-sitter 02:44 Collaborative editing 03:03 Graphical experiments 03:41 Community 04:00 libera.chat """]]
That way, updating the talk pages with chapter descriptions should be less reliant on my Emacs Lisp functions for generating HTML, so it's more likely to be something other people can do.
If you happen to be interested in Emacs and you're planning to watch the talks or Q&A sessions anyway, you can help add chapter markers to videos that don't have them yet. You can either edit the wiki yourself or e-mail me chapter timestamps at . You can also help out by cross-referencing the chapter timestamps with the discussion session on the page, so that people reading the questions can see where to find the answers. If you're feeling extra-helpful, you could even write down the answers for easy reference.
Here are a few pages that have long Q&A sessions. I've linked to the autogenerated captions in the Discussion sections.
- https://emacsconf.org/2021/talks/bindat/
- https://emacsconf.org/2021/talks/faster/
- https://emacsconf.org/2021/talks/janitor/
- https://emacsconf.org/2021/talks/maintainers/
You can call dibs by editing https://etherpad.wikimedia.org/p/emacsconf-2021-volunteers .
Little steps towards making things easier to find! =)
Behind the scenes
I used the auto-generated captions from YouTube as a starting point,
since I could skim them easily. I found that the .ass format was
easier to speed-read than the .vtt format, so I used ffmpeg to convert
them. Then I used emacsconf-subed-mark-chapter
from emacsconf-subed to
capture the timestamps as a .vtt
file.
This is what part of the autogenerated captions looks like:
... Dialogue: 0,0:01:16.11,0:01:18.11,Default,,0,0,0,,First of all, in your opinion, what is Dialogue: 0,0:01:18.11,0:01:20.11,Default,,0,0,0,,Emacs' achilles heel? it's obviously a Dialogue: 0,0:01:20.11,0:01:22.35,Default,,0,0,0,,powerful tool but no tool is perfect ...
and this is part of the chapters file I made:
00:00:26.319 --> 00:03:09.598 In your opinion, what is Emacs' Achilles heel? 00:03:09.599 --> 00:05:06.959 What is your opinion about the documentation of Emacs in other languages? ...
I converted the timestamps to a simple text format handy for including in video descriptions and on the wiki.
[[!template id="chapters" vidid="qanda" data=""" 00:00 Thanks 00:26 In your opinion, what is Emacs' Achilles heel? 03:09 What is your opinion about the documentation of Emacs in other languages? ... ]]
A number of Emacs users browse the web without Javascript, so I wanted the chapter information to be available even then. Putting all the data into a pre tag seems like the easiest way to do it with an ikiwiki template. Here's the template I used:
<pre class="chapters" data-target="<TMPL_VAR vidid>"> <TMPL_VAR data> </pre>
I also modified the IkiWiki htmlscrubber.pm
plugin to allow the attributes I wanted, like data-target
and data-start
.
If Javascript was enabled, I wanted people to be able to click on the chapters in order to jump to the right spot in the video. I split the content into lines, parsed out the timestamps, and replaced the pre tag with the list of links. I also added the chapters as a hidden track in the video so that I could use the cuechange
event to highlight the current chapter. This is what I added to the page.tmpl
:
<script> // @license magnet:?xt=urn:btih:90dc5c0be029de84e523b9b3922520e79e0e6f08&dn=cc0.txt txt CC0-1.0 // Copyright (c) 2021 Sacha Chua - CC0 Public Domain function displayChapters(elem) { var i; var chapter; var list = document.createElement('ol'); list.setAttribute('class', 'chapters'); var link; var target = elem.getAttribute('data-target'); var video = document.getElementById(target); var track; if (video) { track = video.addTextTrack('chapters'); track.mode = 'hidden'; } var chapters = elem.textContent.split(/[ \t]*\n+[ \t]*/).forEach(function(line) { var m = (line.match(/^(([0-9]+:)?[0-9]+:[0-9]+)[ \t]+(.*)/)); if (m) { var start = m[1]; var text = m[3]; chapter = document.createElement('li'); link = document.createElement('a'); link.setAttribute('href', '#'); link.setAttribute('data-video', target); link.setAttribute('data-start', start); link.setAttribute('data-start-s', parseSeconds(start)); link.appendChild(document.createTextNode(m[1] + ' ' + text)); link.onclick = handleSubtitleClick; chapter.appendChild(link); list.appendChild(chapter); if (track) { var time = parseSeconds(start); if (track.cues.length > 0) { track.cues[track.cues.length - 1].endTime = time - 1; } track.addCue(new VTTCue(time, time, text)); } } }) if (track && track.cues.length > 0) { video.addEventListener('durationchange', function() { track.cues[track.cues.length - 1].endTime = video.duration; }); track.addEventListener('cuechange', function() { if (!this.activeCues[0]) return; if (list.querySelector('.current')) { list.querySelector('.current').className = ''; } var chapter; if (chapter = list.querySelector('a[data-start-s="' + this.activeCues[0].startTime + '"]')) { chapter.parentNode.className = 'current'; } }); } elem.parentNode.replaceChild(list, elem); } document.querySelectorAll('pre.chapters').forEach(displayChapters); // @license-end </script>
handleSubtitleClick
is also part of the JS on that page. It sets the current time of the video and scrolls so that the video is in view.