From 66b3f5f472ac5fddf0c0e43181f7718af9075d83 Mon Sep 17 00:00:00 2001 From: Sacha Chua Date: Tue, 25 Oct 2022 09:07:36 -0400 Subject: process-captions --- roles/caption/tasks/main.yml | 25 ++- roles/caption/templates/caption.sh | 18 --- roles/caption/templates/process-captions.py | 231 ++++++++++++++++++++++++++++ talks.json | 2 +- 4 files changed, 254 insertions(+), 22 deletions(-) delete mode 100755 roles/caption/templates/caption.sh create mode 100755 roles/caption/templates/process-captions.py 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 + +# 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 -- cgit v1.2.3