summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSacha Chua <sacha@sachachua.com>2022-10-25 09:07:36 -0400
committerSacha Chua <sacha@sachachua.com>2022-10-25 09:07:36 -0400
commit66b3f5f472ac5fddf0c0e43181f7718af9075d83 (patch)
tree20b8a35aa403c8b6f453d061f09185351b9addd0
parent8637995c0f20672553c192907a36d3c8519b61d4 (diff)
downloademacsconf-ansible-66b3f5f472ac5fddf0c0e43181f7718af9075d83.tar.xz
emacsconf-ansible-66b3f5f472ac5fddf0c0e43181f7718af9075d83.zip
process-captions
-rw-r--r--roles/caption/tasks/main.yml25
-rwxr-xr-xroles/caption/templates/caption.sh18
-rwxr-xr-xroles/caption/templates/process-captions.py231
-rw-r--r--talks.json2
4 files changed, 254 insertions, 22 deletions
diff --git a/roles/caption/tasks/main.yml b/roles/caption/tasks/main.yml
index 7bf45d0..6396339 100644
--- a/roles/caption/tasks/main.yml
+++ b/roles/caption/tasks/main.yml
@@ -3,12 +3,31 @@
name:
- python3-pip
- ffmpeg
+ - cmake
+ - jq
- name: Install whisper
ansible.builtin.pip:
name: git+https://github.com/openai/whisper.git
+- name: Install lhotse
+ ansible.builtin.pip:
+ name:
+ - lhotse
+ - webvtt-py
+ - tqdm
+ - torchaudio
+ - num2words
- name: Copy the shell script
- tags: caption-sh
+ tags: process-captions
template:
- src: caption.sh
- dest: current
+ src: process-captions.py
+ dest: /data/emacsconf/{{ emacsconf_year }}/process-captions.py
mode: 0755
+ owner: sachac
+ group: org
+- name: Copy talks.json
+ tags: talks-json
+ template:
+ src: talks.json
+ dest: /data/emacsconf/{{ emacsconf_year }}/talks.json
+ owner: sachac
+ group: org
diff --git a/roles/caption/templates/caption.sh b/roles/caption/templates/caption.sh
deleted file mode 100755
index 9600a3c..0000000
--- a/roles/caption/templates/caption.sh
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-# {{ ansible_managed }}
-FILE="$1"
-MODEL="${2:small}"
-AUDIO=$(basename "$FILE" | sed s/\\.[a-z][a-z][a-z][a-z]?$//).ogg
-if [[ ! -f $AUDIO ]]; then
- if [[ "$FILE" == *webm ]]; then
- ffmpeg -y -i "$FILE" -acodec copy -vn $AUDIO
- else
- ffmpeg -y -i "$FILE" -acodec libvorbis -vn $AUDIO
- fi
-fi
-date > $AUDIO-$MODEL.log
-time whisper $AUDIO --model $MODEL --threads 12 >> $AUDIO-$MODEL.log
-for EXT in vtt txt srt; do
- mv $AUDIO.$EXT $(basename -s .webm.$EXT $AUDIO.$EXT)
-done
-date >> $AUDIO-$MODEL.log
diff --git a/roles/caption/templates/process-captions.py b/roles/caption/templates/process-captions.py
new file mode 100755
index 0000000..6ad890a
--- /dev/null
+++ b/roles/caption/templates/process-captions.py
@@ -0,0 +1,231 @@
+#!/usr/bin/python3
+"""Use OpenAI Whisper to automatically generate captions for the video files in the specified directory."""
+
+# {{ ansible_managed }}
+
+# The MIT License (MIT)
+# Copyright © 2022 Sacha Chua <sacha@sachachua.com>
+
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the “Software”), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from collections import defaultdict
+import subprocess
+import datetime
+import sys
+import webvtt
+import xml.etree.ElementTree as ET
+from lhotse import RecordingSet, Recording, AudioSource, SupervisionSegment, SupervisionSet, create_cut_set_eager, align_with_torchaudio, CutSet, annotate_with_whisper
+from tqdm import tqdm
+import whisper
+import re
+import os
+import json
+import torch
+
+THREADS = 12
+VIDEO_REGEXP = '\.(webm|mov)$'
+AUDIO_REGEXP = '\.(ogg|opus)$'
+ALWAYS = False
+TRIM_AUDIO = False
+MODEL = os.environ.get('MODEL', 'large') # Set to tiny for testing
+JSON_FILE = '/data/emacsconf/2022/talks.json'
+
+def get_slug_from_filename(filename):
+ m = re.search('emacsconf-[0-9]+-([a-z]+)--', filename)
+ if m:
+ return m.group(1)
+ else:
+ return os.path.basename(os.path.dirname(filename))
+
+def get_files_to_work_on(directory):
+ """Return the list of audio files to work on.
+ The specified directory is checked recursively.
+ Skip any videos that already have caption files.
+
+ Convert any videos that don't already have audio files, and return the audio files instead.
+ When there are multiple videos and audio files for a talk, pick one.
+ """
+ info = defaultdict(lambda: {}, {})
+ directory = os.path.expanduser(directory)
+ for folder, subs, files in os.walk(directory):
+ for filename in files:
+ f = os.path.join(folder, filename)
+ slug = get_slug_from_filename(f)
+ info[slug]['slug'] = slug
+ if re.search(AUDIO_REGEXP, filename):
+ info[slug]['audio'] = f
+ elif re.search(VIDEO_REGEXP, filename):
+ info[slug]['video'] = f
+ elif re.search('vtt$', filename):
+ info[slug]['vtt'] = f
+ elif re.search('srv2$', filename):
+ info[slug]['srv2'] = f
+ needs_work = []
+ if JSON_FILE:
+ with open(JSON_FILE) as f:
+ talks = json.load(f)['talks']
+ for key, val in info.items():
+ if not 'video' in val and not 'audio' in val: continue
+ if talks:
+ talk = next(filter(lambda talk: talk['slug'] == val['slug'], talks), None)
+ if talk:
+ val['base'] = os.path.join(os.path.dirname(val['video'] or val['audio']),
+ base_name(talk['video-slug']))
+ else:
+ val['base'] = os.path.join(os.path.dirname(val['video'] or val['audio']),
+ base_name(val['video'] or val['audio']))
+ if ALWAYS or (not 'vtt' in val or not 'srv2' in val):
+ if not 'audio' in val and 'video' in val:
+ # No audio, need to convert it
+ val = extract_audio(val)
+ needs_work.append(val)
+ return needs_work
+
+def extract_audio(work):
+ output = subprocess.check_output(['ffprobe', video_file], stderr=subprocess.STDOUT)
+ extension = 'opus'
+ if 'Audio: vorbis' in output.decode():
+ extension = 'ogg'
+ new_file = os.path.join(os.path.dirname(video_file), base_name(video_file) + '.' + extension)
+ acodec = 'copy' if re.search('webm$', video_file) else 'libopus'
+ log("Extracting audio from %s acodec %s" % (video_file, acodec))
+ output = subprocess.check_output(['ffmpeg', '-y', '-i', video_file, '-acodec', acodec, '-vn', new_file], stderr=subprocess.STDOUT)
+ work['audio'] = new_file
+ return work
+
+def to_sec(time_str):
+ "Convert a WebVTT time into seconds."
+ h, m, s, ms = re.split('[\\.:]', time_str)
+ return int(h) * 3600 + int(m) * 60 + int(s) + (int(ms) / 1000)
+
+def log(s):
+ print(datetime.datetime.now(), s)
+
+def clean_up_timestamps(result):
+ segs = list(result['segments'])
+ seg_len = len(segs)
+ for i, seg in enumerate(segs[:-1]):
+ seg['end'] = min(segs[i + 1]['start'] - 0.001, seg['end'])
+ result['segments'] = segs
+ return result
+
+def generate_captions(work):
+ """Generate a VTT file based on the audio file."""
+ log("Generating captions")
+ new_file = work['base'] + '.vtt'
+ model = whisper.load_model(MODEL, device="cuda" if torch.cuda.is_available() else "cpu")
+ audio = whisper.load_audio(work['audio'])
+ if TRIM_AUDIO:
+ audio = whisper.pad_or_trim(audio)
+ result = model.transcribe(audio, verbose=True)
+ result = clean_up_timestamps(result)
+ with open(new_file, 'w') as vtt:
+ whisper.utils.write_vtt(result['segments'], file=vtt)
+ work['vtt'] = new_file
+ if 'srv2' in work: del work['srv2']
+ return work
+
+def generate_srv2(work):
+ """Generate a SRV2 file."""
+ log("Generating SRV2")
+ recs = RecordingSet.from_recordings([Recording.from_file(work['audio'])])
+ rec_id = recs[0].id
+ captions = []
+ for i, caption in enumerate(webvtt.read(work['vtt'])):
+ if TRIM_AUDIO and i > 2: break
+ captions.append(SupervisionSegment(id=rec_id + '-sup' + '%05d' % i, channel=recs[0].channel_ids[0], recording_id=rec_id, start=to_sec(caption.start), duration=to_sec(caption.end) - to_sec(caption.start), text=caption.text, language='English'))
+ sups = SupervisionSet.from_segments(captions)
+ main = CutSet.from_manifests(recordings=recs, supervisions=sups)
+ work['cuts'] = main.trim_to_supervisions(keep_overlapping=False,keep_all_channels=True)
+ cuts_aligned = align_with_torchaudio(work['cuts'])
+ root = ET.Element("timedtext")
+ doc = ET.SubElement(root, "window")
+ for line, aligned in enumerate(cuts_aligned):
+ # Numbers are weird
+ words = re.split(' ', captions[line].text)
+ tokenized_words = [re.sub('[^\'A-Z0-9]', '', w.upper()) for w in words]
+ if len(aligned.supervisions) == 0:
+ print(captions[line], aligned)
+ continue
+ aligned_words = list(aligned.supervisions[0].alignment['word'])
+ aligned_index = 0
+ aligned_len = len(aligned_words)
+ word_index = 0
+ word_len = len(words)
+ while word_index < word_len and aligned_index < aligned_len:
+ # log("Choosing %s %s" % (words[word_index], aligned_words[aligned_index].symbol))
+ ET.SubElement(doc, 'text',
+ t=str(float(aligned_words[aligned_index].start)*1000),
+ d=str(float(aligned_words[aligned_index].duration)*1000),
+ w="1",
+ append="1").text = words[word_index]
+ if tokenized_words[word_index] != aligned_words[aligned_index].symbol and word_index < word_len - 1:
+ # Scan ahead for a word that maches the next word, but don't go too far
+ cur_aligned = aligned_index
+ while aligned_index < aligned_len and aligned_index < cur_aligned + 5 and aligned_words[aligned_index].symbol != tokenized_words[word_index + 1]:
+ log("Sliding to match %s %d %s" % (tokenized_words[word_index + 1], aligned_index, aligned_words[aligned_index].symbol))
+ aligned_index = aligned_index + 1
+ if not aligned_words[aligned_index].symbol == tokenized_words[word_index + 1]:
+ log("Resetting, couldn't find")
+ aligned_index = cur_aligned + 1
+ else:
+ aligned_index = aligned_index + 1
+ word_index = word_index + 1
+ tree = ET.ElementTree(root)
+ work['srv2'] = work['base'] + '.srv2'
+ with open(work['srv2'], "w") as f:
+ tree.write(f.buffer)
+ return work
+
+def base_name(s):
+ """
+ Return the base name of file so that we can add extensions to it.
+ Remove tokens like --normalized, --recoded, etc.
+ Make sure the filename has either --main or --questions.
+ """
+ s = os.path.basename(s)
+ type = 'questions' if '--questions.' in s else 'main'
+ if TRIM_AUDIO:
+ type = 'test'
+ match = re.match('^(emacsconf-[0-9]+-[a-z]+--.*?--.*?)(--|\.)', s)
+ if (match):
+ return match.group(1) + '--' + type
+ else:
+ return os.path.splitext(s)[0] + '--' + type
+# assert(base_name('/home/sachac/current/sqlite/emacsconf-2022-sqlite--using-sqlite-as-a-data-source-a-framework-and-an-example--andrew-hyatt--normalized.webm.vtt') == 'emacsconf-2022-sqlite--using-sqlite-as-a-data-source-a-framework-and-an-example--andrew-hyatt--main')
+
+log(f"MODEL {MODEL} ALWAYS {ALWAYS} TRIM_AUDIO {TRIM_AUDIO}")
+directory = sys.argv[1] if len(sys.argv) > 1 else "~/current"
+needs_work = get_files_to_work_on(directory)
+if THREADS > 0:
+ torch.set_num_threads(THREADS)
+for work in needs_work:
+ log("Started processing %s" % work['base'])
+ if work['audio']:
+ if ALWAYS or not 'vtt' in work:
+ work = generate_captions(work)
+ if ALWAYS or not 'srv2' in work:
+ work = generate_srv2(work)
+# print("Aligning words", audio_file, datetime.datetime.now())
+# word_cuts = align_words(cuts)
+# convert_cuts_to_word_timing(audio_file, word_cuts)
+ log("Done %s" % str(work['base']))
+
diff --git a/talks.json b/talks.json
index 85c716d..4d10ec1 100644
--- a/talks.json
+++ b/talks.json
@@ -1 +1 @@
-{"talks":[{"start-time":"2022-12-03T14:05:00+0000","end-time":"2022-12-03T14:25:00+0000","slug":"journalism","title":"Emacs journalism (or everything's a nail if you hit it with Emacs)","speakers":"Alfred Zanini","pronouns":"he/they","pronunciation":null,"url":"2022/talks/journalism","track":"General"},{"start-time":"2022-12-03T14:45:00+0000","end-time":"2022-12-03T15:05:00+0000","slug":"school","title":"Back to school with Emacs","speakers":"Daniel Rösel","pronouns":null,"pronunciation":"ˈrøːzl̩","url":"2022/talks/school","track":"General"},{"start-time":"2022-12-03T15:15:00+0000","end-time":"2022-12-03T15:25:00+0000","slug":"handwritten","title":"How to incorporate handwritten notes into Emacs Orgmode","speakers":"Bala Ramadurai","pronouns":"his/him","pronunciation":null,"url":"2022/talks/handwritten","track":"General"},{"start-time":"2022-12-03T15:55:00+0000","end-time":"2022-12-03T16:15:00+0000","slug":"science","title":"Writing and organizing literature notes for scientific writing","speakers":"Vidianos","pronouns":"nil","pronunciation":null,"url":"2022/talks/science","track":"General"},{"start-time":"2022-12-03T18:00:00+0000","end-time":"2022-12-03T18:10:00+0000","slug":"meetups","title":"Attending and organizing Emacs meetups","speakers":"Bhavin Gandhi","pronouns":"he/him","pronunciation":null,"url":"2022/talks/meetups","track":"General"},{"start-time":"2022-12-03T18:30:00+0000","end-time":"2022-12-03T18:40:00+0000","slug":"buddy","title":"The Emacs Buddy initiative","speakers":"Andrea","pronouns":null,"pronunciation":null,"url":"2022/talks/buddy","track":"General"},{"start-time":"2022-12-03T18:50:00+0000","end-time":"2022-12-03T19:20:00+0000","slug":"community","title":"The ship that builds itself: How we used Emacs to develop a workshop for communities","speakers":"Noorah Alhasan, Joseph Corneli, Leo Vivier","pronouns":"Noorah: she/her, Joseph: he/him, Leo: he/him","pronunciation":null,"url":"2022/talks/community","track":"General"},{"start-time":"2022-12-03T19:50:00+0000","end-time":"2022-12-03T20:10:00+0000","slug":"realestate","title":"Real estate and Org table formulas","speakers":"Daniel Gopar","pronouns":"He/Him","pronunciation":null,"url":"2022/talks/realestate","track":"General"},{"start-time":"2022-12-03T20:20:00+0000","end-time":"2022-12-03T20:40:00+0000","slug":"health","title":"Health data journaling and visualization with Org Mode and GNUplot","speakers":"David O'Toole","pronouns":"he/him","pronunciation":null,"url":"2022/talks/health","track":"General"},{"start-time":"2022-12-03T21:00:00+0000","end-time":"2022-12-03T21:10:00+0000","slug":"jupyter","title":"Edit live Jupyter notebook cells with Emacs","speakers":"Blaine Mooers","pronouns":"he/him","pronunciation":"Blane Moors","url":"2022/talks/jupyter","track":"General"},{"start-time":"2022-12-04T14:05:00+0000","end-time":"2022-12-04T14:25:00+0000","slug":"survey","title":"Results of the 2022 Emacs Survey","speakers":"Timothy","pronouns":"he/him","pronunciation":null,"url":"2022/talks/survey","track":"General"},{"start-time":"2022-12-04T14:35:00+0000","end-time":"2022-12-04T14:45:00+0000","slug":"orgyear","title":"This Year in Org","speakers":"Timothy","pronouns":"he/him","pronunciation":null,"url":"2022/talks/orgyear","track":"General"},{"start-time":"2022-12-04T15:00:00+0000","end-time":"2022-12-04T15:20:00+0000","slug":"rolodex","title":"Build a Zettelkasten with the Hyperbole Rolodex","speakers":"Ramin Honary","pronouns":"he/him","pronunciation":"\"Rah-mean\" (hard-H) \"Ho-na-ree\"","url":"2022/talks/rolodex","track":"General"},{"start-time":"2022-12-04T15:40:00+0000","end-time":"2022-12-04T15:50:00+0000","slug":"orgsuperlinks","title":"Linking headings with org-super-links (poor-man's Zettelkasten)","speakers":"Karl Voit","pronouns":"he/him","pronunciation":null,"url":"2022/talks/orgsuperlinks","track":"General"},{"start-time":"2022-12-04T16:10:00+0000","end-time":"2022-12-04T16:20:00+0000","slug":"buttons","title":"Linking personal info with Hyperbole implicit buttons","speakers":"Mats Lidell","pronouns":"He,Him,His","pronunciation":null,"url":"2022/talks/buttons","track":"General"},{"start-time":"2022-12-04T18:00:00+0000","end-time":"2022-12-04T18:30:00+0000","slug":"hyperorg","title":"Powerful productivity with Hyperbole and Org Mode","speakers":"Robert Weiner","pronouns":null,"pronunciation":"wine-er","url":"2022/talks/hyperorg","track":"General"},{"start-time":"2022-12-04T18:50:00+0000","end-time":"2022-12-04T19:10:00+0000","slug":"workflows","title":"Org workflows for developers","speakers":"George Mauer","pronouns":"he/him/they/ze","pronunciation":null,"url":"2022/talks/workflows","track":"General"},{"start-time":"2022-12-04T19:30:00+0000","end-time":"2022-12-04T19:50:00+0000","slug":"grail","title":"GRAIL---A Generalized Representation and Aggregation of Information Layers","speakers":"Sameer Pradhan","pronouns":"he/him","pronunciation":null,"url":"2022/talks/grail","track":"General"},{"start-time":"2022-12-03T21:30:00+0000","end-time":"2022-12-03T21:40:00+0000","slug":"orgvm","title":"orgvm: a simple HTTP server for org","speakers":"Corwin Brust","pronouns":"he/him/any","pronunciation":null,"url":"2022/talks/orgvm","track":"General"},{"start-time":"2022-12-04T20:20:00+0000","end-time":"2022-12-04T20:40:00+0000","slug":"indieweb","title":"Putting Org Mode on the Indieweb","speakers":"Michael Herstine","pronouns":null,"pronunciation":null,"url":"2022/talks/indieweb","track":"General"},{"start-time":"2022-12-04T21:00:00+0000","end-time":"2022-12-04T21:10:00+0000","slug":"fanfare","title":"Fanfare for the Common Emacs User","speakers":"John Cummings","pronouns":null,"pronunciation":null,"url":"2022/talks/fanfare","track":"General"},{"start-time":"2022-12-04T21:00:00+0000","end-time":"2022-12-04T21:20:00+0000","slug":"localizing","title":"Pre-localizing Emacs","speakers":"Jean-Christophe Helary","pronouns":"he/him","pronunciation":null,"url":"2022/talks/localizing","track":"Development"},{"start-time":"2022-12-03T15:00:00+0000","end-time":"2022-12-03T15:10:00+0000","slug":"treesitter","title":"Tree-sitter beyond syntax highlighting","speakers":"Abin Simon","pronouns":"nil","pronunciation":null,"url":"2022/talks/treesitter","track":"Development"},{"start-time":"2022-12-03T15:20:00+0000","end-time":"2022-12-03T15:40:00+0000","slug":"lspbridge","title":"lsp-bridge: complete asynchronous LSP client","speakers":"Andy Stewart, Matthew Zeng","pronouns":"nil","pronunciation":null,"url":"2022/talks/lspbridge","track":"Development"},{"start-time":"2022-12-03T18:00:00+0000","end-time":"2022-12-03T18:20:00+0000","slug":"sqlite","title":"Using SQLite as a data source: a framework and an example","speakers":"Andrew Hyatt","pronouns":"he/him","pronunciation":null,"url":"2022/talks/sqlite","track":"Development"},{"start-time":"2022-12-03T18:45:00+0000","end-time":"2022-12-03T19:15:00+0000","slug":"mail","title":"Revisiting the anatomy of Emacs mail user agents","speakers":"Mohsen BANAN","pronouns":"he/him","pronunciation":"MO-HH-SS-EN","url":"2022/talks/mail","track":"Development"},{"start-time":"2022-12-03T20:35:00+0000","end-time":"2022-12-03T20:40:00+0000","slug":"eev","title":"Bidirectional links with eev","speakers":"Eduardo Ochs","pronouns":"nil","pronunciation":null,"url":"2022/talks/eev","track":"Development"},{"start-time":"2022-12-03T20:50:00+0000","end-time":"2022-12-03T20:55:00+0000","slug":"python","title":"Short hyperlinks to Python docs","speakers":"Eduardo Ochs","pronouns":null,"pronunciation":null,"url":"2022/talks/python","track":"Development"},{"start-time":"2022-12-03T19:50:00+0000","end-time":"2022-12-03T20:10:00+0000","slug":"maint","title":"Maintaining the Maintainers: Attribution as an Economic Model for Open Source","speakers":"Sid Kasivajhula","pronouns":"any pronouns, commonly he/him","pronunciation":null,"url":"2022/talks/maint","track":"Development"},{"start-time":"2022-12-03T21:05:00+0000","end-time":"2022-12-03T21:35:00+0000","slug":"haskell","title":"Haskell code exploration with Emacs","speakers":"Yuchen Pei","pronouns":null,"pronunciation":"he/him/himself/his/his","url":"2022/talks/haskell","track":"Development"},{"start-time":"2022-12-04T15:00:00+0000","end-time":"2022-12-04T15:20:00+0000","slug":"rde","title":"rde Emacs introduction","speakers":"Andrew Tropin","pronouns":"he/him","pronunciation":null,"url":"2022/talks/rde","track":"Development"},{"start-time":"2022-12-04T15:45:00+0000","end-time":"2022-12-04T15:55:00+0000","slug":"justl","title":"justl: Driving recipes within Emacs","speakers":"Sibi Prabakaran","pronouns":"He/Him","pronunciation":null,"url":"2022/talks/justl","track":"Development"},{"start-time":"2022-12-04T16:05:00+0000","end-time":"2022-12-04T16:35:00+0000","slug":"tramp","title":"Elisp and the TRAMP: How to NOT write code you don't have to","speakers":"Grant Shangreaux","pronouns":"he/him","pronunciation":"Shang-groo or Shang-grow are fine","url":"2022/talks/tramp","track":"Development"},{"start-time":"2022-12-04T18:00:00+0000","end-time":"2022-12-04T18:10:00+0000","slug":"detached","title":"Getting detached from Emacs","speakers":"Niklas Eklund","pronouns":"he/him","pronunciation":null,"url":"2022/talks/detached","track":"Development"},{"start-time":"2022-12-04T18:35:00+0000","end-time":"2022-12-04T18:45:00+0000","slug":"eshell","title":"Top 10 reasons why you should be using Eshell","speakers":"Howard Abrams","pronouns":"he/him","pronunciation":null,"url":"2022/talks/eshell","track":"Development"},{"start-time":"2022-12-04T19:10:00+0000","end-time":"2022-12-04T19:30:00+0000","slug":"async","title":"Emacs was async before async was cool","speakers":"Michael Herstine","pronouns":null,"pronunciation":null,"url":"2022/talks/async","track":"Development"},{"start-time":"2022-12-03T15:50:00+0000","end-time":"2022-12-03T16:00:00+0000","slug":"asmblox","title":"asm-blox: a game based on WebAssembly that no one asked for","speakers":"Zachary Romero","pronouns":"nil","pronunciation":null,"url":"2022/talks/asmblox","track":"Development"},{"start-time":"2022-12-04T20:05:00+0000","end-time":"2022-12-04T20:25:00+0000","slug":"dbus","title":"The Wheels on D-Bus","speakers":"Ian Eure","pronouns":"he/him/his","pronunciation":"ee-uhn you-er","url":"2022/talks/dbus","track":"Development"},{"start-time":"2022-12-03T16:25:00+0000","end-time":"2022-12-03T16:35:00+0000","slug":"wayland","title":"Emacs should become a Wayland compositor","speakers":"Michael Bauer","pronouns":null,"pronunciation":null,"url":"2022/talks/wayland","track":"Development"},{"start-time":"2022-12-04T23:00:00+0000","end-time":"2022-12-04T23:05:00+0000","slug":"news","title":"Emacs News highlights","speakers":"Sacha Chua","pronouns":"she/her","pronunciation":"SA-sha CHEW-ah","url":"2022/talks/news","track":null}]} \ No newline at end of file
+{"talks":[{"start-time":"2022-12-03T14:05:00+0000","end-time":"2022-12-03T14:25:00+0000","slug":"journalism","title":"Emacs journalism (or everything's a nail if you hit it with Emacs)","speakers":"Alfred Zanini","pronouns":"he/they","pronunciation":null,"url":"2022/talks/journalism","track":"General","video-slug":"emacsconf-2022-journalism--emacs-journalism-or-everythings-a-nail-if-you-hit-it-with-emacs--alfred-zanini"},{"start-time":"2022-12-03T14:45:00+0000","end-time":"2022-12-03T15:05:00+0000","slug":"school","title":"Back to school with Emacs","speakers":"Daniel R\\303\\266sel","pronouns":null,"pronunciation":"\\313\\210r\\303\\270\\313\\220zl\\314\\251","url":"2022/talks/school","track":"General","video-slug":"emacsconf-2022-school--back-to-school-with-emacs--daniel-rosel"},{"start-time":"2022-12-03T15:15:00+0000","end-time":"2022-12-03T15:25:00+0000","slug":"handwritten","title":"How to incorporate handwritten notes into Emacs Orgmode","speakers":"Bala Ramadurai","pronouns":"his/him","pronunciation":null,"url":"2022/talks/handwritten","track":"General","video-slug":"emacsconf-2022-handwritten--how-to-incorporate-handwritten-notes-into-emacs-orgmode--bala-ramadurai"},{"start-time":"2022-12-03T15:55:00+0000","end-time":"2022-12-03T16:15:00+0000","slug":"science","title":"Writing and organizing literature notes for scientific writing","speakers":"Vidianos","pronouns":"nil","pronunciation":null,"url":"2022/talks/science","track":"General","video-slug":"emacsconf-2022-science--writing-and-organizing-literature-notes-for-scientific-writing--vidianos"},{"start-time":"2022-12-03T18:00:00+0000","end-time":"2022-12-03T18:10:00+0000","slug":"meetups","title":"Attending and organizing Emacs meetups","speakers":"Bhavin Gandhi","pronouns":"he/him","pronunciation":null,"url":"2022/talks/meetups","track":"General","video-slug":"emacsconf-2022-meetups--attending-and-organizing-emacs-meetups--bhavin-gandhi"},{"start-time":"2022-12-03T18:30:00+0000","end-time":"2022-12-03T18:40:00+0000","slug":"buddy","title":"The Emacs Buddy initiative","speakers":"Andrea","pronouns":null,"pronunciation":null,"url":"2022/talks/buddy","track":"General","video-slug":"emacsconf-2022-buddy--the-emacs-buddy-initiative--andrea"},{"start-time":"2022-12-03T18:50:00+0000","end-time":"2022-12-03T19:20:00+0000","slug":"community","title":"The ship that builds itself: How we used Emacs to develop a workshop for communities","speakers":"Noorah Alhasan, Joseph Corneli, Leo Vivier","pronouns":"Noorah: she/her, Joseph: he/him, Leo: he/him","pronunciation":null,"url":"2022/talks/community","track":"General","video-slug":"emacsconf-2022-community--the-ship-that-builds-itself-how-we-used-emacs-to-develop-a-workshop-for-communities--noorah-alhasan-joseph-corneli-leo-vivier"},{"start-time":"2022-12-03T19:50:00+0000","end-time":"2022-12-03T20:10:00+0000","slug":"realestate","title":"Real estate and Org table formulas","speakers":"Daniel Gopar","pronouns":"He/Him","pronunciation":null,"url":"2022/talks/realestate","track":"General","video-slug":"emacsconf-2022-realestate--real-estate-and-org-table-formulas--daniel-gopar"},{"start-time":"2022-12-03T20:20:00+0000","end-time":"2022-12-03T20:40:00+0000","slug":"health","title":"Health data journaling and visualization with Org Mode and GNUplot","speakers":"David O'Toole","pronouns":"he/him","pronunciation":null,"url":"2022/talks/health","track":"General","video-slug":"emacsconf-2022-health--health-data-journaling-and-visualization-with-org-mode-and-gnuplot--david-otoole"},{"start-time":"2022-12-03T21:00:00+0000","end-time":"2022-12-03T21:10:00+0000","slug":"jupyter","title":"Edit live Jupyter notebook cells with Emacs","speakers":"Blaine Mooers","pronouns":"he/him","pronunciation":"Blane Moors","url":"2022/talks/jupyter","track":"General","video-slug":"emacsconf-2022-jupyter--edit-live-jupyter-notebook-cells-with-emacs--blaine-mooers"},{"start-time":"2022-12-04T14:05:00+0000","end-time":"2022-12-04T14:25:00+0000","slug":"survey","title":"Results of the 2022 Emacs Survey","speakers":"Timothy","pronouns":"he/him","pronunciation":null,"url":"2022/talks/survey","track":"General","video-slug":"emacsconf-2022-survey--results-of-the-2022-emacs-survey--timothy"},{"start-time":"2022-12-04T14:35:00+0000","end-time":"2022-12-04T14:45:00+0000","slug":"orgyear","title":"This Year in Org","speakers":"Timothy","pronouns":"he/him","pronunciation":null,"url":"2022/talks/orgyear","track":"General","video-slug":"emacsconf-2022-orgyear--this-year-in-org--timothy"},{"start-time":"2022-12-04T15:00:00+0000","end-time":"2022-12-04T15:20:00+0000","slug":"rolodex","title":"Build a Zettelkasten with the Hyperbole Rolodex","speakers":"Ramin Honary","pronouns":"he/him","pronunciation":"\"Rah-mean\" (hard-H) \"Ho-na-ree\"","url":"2022/talks/rolodex","track":"General","video-slug":"emacsconf-2022-rolodex--build-a-zettelkasten-with-the-hyperbole-rolodex--ramin-honary"},{"start-time":"2022-12-04T15:40:00+0000","end-time":"2022-12-04T15:50:00+0000","slug":"orgsuperlinks","title":"Linking headings with org-super-links (poor-man's Zettelkasten)","speakers":"Karl Voit","pronouns":"he/him","pronunciation":null,"url":"2022/talks/orgsuperlinks","track":"General","video-slug":"emacsconf-2022-orgsuperlinks--linking-headings-with-orgsuperlinks-poormans-zettelkasten--karl-voit"},{"start-time":"2022-12-04T16:10:00+0000","end-time":"2022-12-04T16:20:00+0000","slug":"buttons","title":"Linking personal info with Hyperbole implicit buttons","speakers":"Mats Lidell","pronouns":"He,Him,His","pronunciation":null,"url":"2022/talks/buttons","track":"General","video-slug":"emacsconf-2022-buttons--linking-personal-info-with-hyperbole-implicit-buttons--mats-lidell"},{"start-time":"2022-12-04T18:00:00+0000","end-time":"2022-12-04T18:30:00+0000","slug":"hyperorg","title":"Powerful productivity with Hyperbole and Org Mode","speakers":"Robert Weiner","pronouns":null,"pronunciation":"wine-er","url":"2022/talks/hyperorg","track":"General","video-slug":"emacsconf-2022-hyperorg--powerful-productivity-with-hyperbole-and-org-mode--robert-weiner"},{"start-time":"2022-12-04T18:50:00+0000","end-time":"2022-12-04T19:10:00+0000","slug":"workflows","title":"Org workflows for developers","speakers":"George Mauer","pronouns":"he/him/they/ze","pronunciation":null,"url":"2022/talks/workflows","track":"General","video-slug":"emacsconf-2022-workflows--org-workflows-for-developers--george-mauer"},{"start-time":"2022-12-04T19:30:00+0000","end-time":"2022-12-04T19:50:00+0000","slug":"grail","title":"GRAIL---A Generalized Representation and Aggregation of Information Layers","speakers":"Sameer Pradhan","pronouns":"he/him","pronunciation":null,"url":"2022/talks/grail","track":"General","video-slug":"emacsconf-2022-grail--graila-generalized-representation-and-aggregation-of-information-layers--sameer-pradhan"},{"start-time":"2022-12-03T21:30:00+0000","end-time":"2022-12-03T21:40:00+0000","slug":"orgvm","title":"orgvm: a simple HTTP server for org","speakers":"Corwin Brust","pronouns":"he/him/any","pronunciation":null,"url":"2022/talks/orgvm","track":"General","video-slug":"emacsconf-2022-orgvm--orgvm-a-simple-http-server-for-org--corwin-brust"},{"start-time":"2022-12-04T20:20:00+0000","end-time":"2022-12-04T20:40:00+0000","slug":"indieweb","title":"Putting Org Mode on the Indieweb","speakers":"Michael Herstine","pronouns":null,"pronunciation":null,"url":"2022/talks/indieweb","track":"General","video-slug":"emacsconf-2022-indieweb--putting-org-mode-on-the-indieweb--michael-herstine"},{"start-time":"2022-12-04T21:00:00+0000","end-time":"2022-12-04T21:10:00+0000","slug":"fanfare","title":"Fanfare for the Common Emacs User","speakers":"John Cummings","pronouns":null,"pronunciation":null,"url":"2022/talks/fanfare","track":"General","video-slug":"emacsconf-2022-fanfare--fanfare-for-the-common-emacs-user--john-cummings"},{"start-time":"2022-12-04T21:00:00+0000","end-time":"2022-12-04T21:20:00+0000","slug":"localizing","title":"Pre-localizing Emacs","speakers":"Jean-Christophe Helary","pronouns":"he/him","pronunciation":null,"url":"2022/talks/localizing","track":"Development","video-slug":"emacsconf-2022-localizing--prelocalizing-emacs--jeanchristophe-helary"},{"start-time":"2022-12-03T15:00:00+0000","end-time":"2022-12-03T15:10:00+0000","slug":"treesitter","title":"Tree-sitter beyond syntax highlighting","speakers":"Abin Simon","pronouns":"nil","pronunciation":null,"url":"2022/talks/treesitter","track":"Development","video-slug":"emacsconf-2022-treesitter--treesitter-beyond-syntax-highlighting--abin-simon"},{"start-time":"2022-12-03T15:20:00+0000","end-time":"2022-12-03T15:40:00+0000","slug":"lspbridge","title":"lsp-bridge: complete asynchronous LSP client","speakers":"Andy Stewart, Matthew Zeng","pronouns":"nil","pronunciation":null,"url":"2022/talks/lspbridge","track":"Development","video-slug":"emacsconf-2022-lspbridge--lspbridge-complete-asynchronous-lsp-client--andy-stewart-matthew-zeng"},{"start-time":"2022-12-03T18:00:00+0000","end-time":"2022-12-03T18:20:00+0000","slug":"sqlite","title":"Using SQLite as a data source: a framework and an example","speakers":"Andrew Hyatt","pronouns":"he/him","pronunciation":null,"url":"2022/talks/sqlite","track":"Development","video-slug":"emacsconf-2022-sqlite--using-sqlite-as-a-data-source-a-framework-and-an-example--andrew-hyatt"},{"start-time":"2022-12-03T18:45:00+0000","end-time":"2022-12-03T19:15:00+0000","slug":"mail","title":"Revisiting the anatomy of Emacs mail user agents","speakers":"Mohsen BANAN","pronouns":"he/him","pronunciation":"MO-HH-SS-EN","url":"2022/talks/mail","track":"Development","video-slug":"emacsconf-2022-mail--revisiting-the-anatomy-of-emacs-mail-user-agents--mohsen-banan"},{"start-time":"2022-12-03T20:35:00+0000","end-time":"2022-12-03T20:40:00+0000","slug":"eev","title":"Bidirectional links with eev","speakers":"Eduardo Ochs","pronouns":"nil","pronunciation":null,"url":"2022/talks/eev","track":"Development","video-slug":"emacsconf-2022-eev--bidirectional-links-with-eev--eduardo-ochs"},{"start-time":"2022-12-03T20:50:00+0000","end-time":"2022-12-03T20:55:00+0000","slug":"python","title":"Short hyperlinks to Python docs","speakers":"Eduardo Ochs","pronouns":null,"pronunciation":null,"url":"2022/talks/python","track":"Development","video-slug":"emacsconf-2022-python--short-hyperlinks-to-python-docs--eduardo-ochs"},{"start-time":"2022-12-03T19:50:00+0000","end-time":"2022-12-03T20:10:00+0000","slug":"maint","title":"Maintaining the Maintainers: Attribution as an Economic Model for Open Source","speakers":"Sid Kasivajhula","pronouns":"any pronouns, commonly he/him","pronunciation":null,"url":"2022/talks/maint","track":"Development","video-slug":"emacsconf-2022-maint--maintaining-the-maintainers-attribution-as-an-economic-model-for-open-source--sid-kasivajhula"},{"start-time":"2022-12-03T21:05:00+0000","end-time":"2022-12-03T21:35:00+0000","slug":"haskell","title":"Haskell code exploration with Emacs","speakers":"Yuchen Pei","pronouns":null,"pronunciation":"he/him/himself/his/his","url":"2022/talks/haskell","track":"Development","video-slug":"emacsconf-2022-haskell--haskell-code-exploration-with-emacs--yuchen-pei"},{"start-time":"2022-12-04T15:00:00+0000","end-time":"2022-12-04T15:20:00+0000","slug":"rde","title":"rde Emacs introduction","speakers":"Andrew Tropin","pronouns":"he/him","pronunciation":null,"url":"2022/talks/rde","track":"Development","video-slug":"emacsconf-2022-rde--rde-emacs-introduction--andrew-tropin"},{"start-time":"2022-12-04T15:45:00+0000","end-time":"2022-12-04T15:55:00+0000","slug":"justl","title":"justl: Driving recipes within Emacs","speakers":"Sibi Prabakaran","pronouns":"He/Him","pronunciation":null,"url":"2022/talks/justl","track":"Development","video-slug":"emacsconf-2022-justl--justl-driving-recipes-within-emacs--sibi-prabakaran"},{"start-time":"2022-12-04T16:05:00+0000","end-time":"2022-12-04T16:35:00+0000","slug":"tramp","title":"Elisp and the TRAMP: How to NOT write code you don't have to","speakers":"Grant Shangreaux","pronouns":"he/him","pronunciation":"Shang-groo or Shang-grow are fine","url":"2022/talks/tramp","track":"Development","video-slug":"emacsconf-2022-tramp--elisp-and-the-tramp-how-to-not-write-code-you-dont-have-to--grant-shangreaux"},{"start-time":"2022-12-04T18:00:00+0000","end-time":"2022-12-04T18:10:00+0000","slug":"detached","title":"Getting detached from Emacs","speakers":"Niklas Eklund","pronouns":"he/him","pronunciation":null,"url":"2022/talks/detached","track":"Development","video-slug":"emacsconf-2022-detached--getting-detached-from-emacs--niklas-eklund"},{"start-time":"2022-12-04T18:35:00+0000","end-time":"2022-12-04T18:45:00+0000","slug":"eshell","title":"Top 10 reasons why you should be using Eshell","speakers":"Howard Abrams","pronouns":"he/him","pronunciation":null,"url":"2022/talks/eshell","track":"Development","video-slug":"emacsconf-2022-eshell--top-10-reasons-why-you-should-be-using-eshell--howard-abrams"},{"start-time":"2022-12-04T19:10:00+0000","end-time":"2022-12-04T19:30:00+0000","slug":"async","title":"Emacs was async before async was cool","speakers":"Michael Herstine","pronouns":null,"pronunciation":null,"url":"2022/talks/async","track":"Development","video-slug":"emacsconf-2022-async--emacs-was-async-before-async-was-cool--michael-herstine"},{"start-time":"2022-12-03T15:50:00+0000","end-time":"2022-12-03T16:00:00+0000","slug":"asmblox","title":"asm-blox: a game based on WebAssembly that no one asked for","speakers":"Zachary Romero","pronouns":"nil","pronunciation":null,"url":"2022/talks/asmblox","track":"Development","video-slug":"emacsconf-2022-asmblox--asmblox-a-game-based-on-webassembly-that-no-one-asked-for--zachary-romero"},{"start-time":"2022-12-04T20:05:00+0000","end-time":"2022-12-04T20:25:00+0000","slug":"dbus","title":"The Wheels on D-Bus","speakers":"Ian Eure","pronouns":"he/him/his","pronunciation":"ee-uhn you-er","url":"2022/talks/dbus","track":"Development","video-slug":"emacsconf-2022-dbus--the-wheels-on-dbus--ian-eure"},{"start-time":"2022-12-03T16:25:00+0000","end-time":"2022-12-03T16:35:00+0000","slug":"wayland","title":"Emacs should become a Wayland compositor","speakers":"Michael Bauer","pronouns":null,"pronunciation":null,"url":"2022/talks/wayland","track":"Development","video-slug":"emacsconf-2022-wayland--emacs-should-become-a-wayland-compositor--michael-bauer"},{"start-time":"2022-12-03T14:00:00+0000","end-time":"2022-12-03T14:05:00+0000","slug":"sat-open","title":"Saturday opening remarks","speakers":null,"pronouns":null,"pronunciation":null,"url":"2022/talks/sat-open","track":"General","video-slug":"emacsconf-2022-sat-open--saturday-opening-remarks"},{"start-time":"2022-12-03T22:00:00+0000","end-time":"2022-12-03T22:05:00+0000","slug":"sat-close","title":"Saturday closing remarks","speakers":null,"pronouns":null,"pronunciation":null,"url":"2022/talks/sat-close","track":"General","video-slug":"emacsconf-2022-sat-close--saturday-closing-remarks"},{"start-time":"2022-12-04T14:00:00+0000","end-time":"2022-12-04T14:05:00+0000","slug":"sun-open","title":"Sunday opening remarks","speakers":null,"pronouns":null,"pronunciation":null,"url":"2022/talks/sun-open","track":"General","video-slug":"emacsconf-2022-sun-open--sunday-opening-remarks"},{"start-time":"2022-12-04T22:00:00+0000","end-time":"2022-12-04T22:10:00+0000","slug":"sun-close","title":"Sunday closing remarks","speakers":null,"pronouns":null,"pronunciation":null,"url":"2022/talks/sun-close","track":"General","video-slug":"emacsconf-2022-sun-close--sunday-closing-remarks"},{"start-time":"2022-12-04T23:00:00+0000","end-time":"2022-12-04T23:05:00+0000","slug":"news","title":"Emacs News highlights","speakers":"Sacha Chua","pronouns":"she/her","pronunciation":"SA-sha CHEW-ah","url":"2022/talks/news","track":null,"video-slug":"emacsconf-2022-news--emacs-news-highlights--sacha-chua"}]} \ No newline at end of file