Skip to content

Instantly share code, notes, and snippets.

@doegox
Created June 27, 2026 17:35
Show Gist options
  • Select an option

  • Save doegox/0710704d2ba2fb93da999f1aaa0ff6b2 to your computer and use it in GitHub Desktop.

Select an option

Save doegox/0710704d2ba2fb93da999f1aaa0ff6b2 to your computer and use it in GitHub Desktop.
Google Slides speaker notes => export as ODP => pdfpc JSON
#!/usr/bin/env python3
import sys
import xml.etree.ElementTree as ET
import json
import os
import zipfile
import tempfile
ns = {
'draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
'presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
'text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
}
def extract_notes(content_xml_path):
tree = ET.parse(content_xml_path)
root = tree.getroot()
pages = root.findall('.//draw:page', ns)
notes = []
for i, page in enumerate(pages):
notes_elem = page.find('.//presentation:notes', ns)
note_text = ''
if notes_elem is not None:
lines = []
for tp in notes_elem.findall('.//text:p', ns):
parts = []
if tp.text:
parts.append(tp.text)
for child in tp:
tag = child.tag.split('}')[-1] if '}' in child.tag else child.tag
if tag == 'line-break':
parts.append('\n')
elif tag == 'tab':
parts.append('\t')
elif tag == 's':
count = child.get('{urn:oasis:names:tc:opendocument:xmlns:text:1.0}c', '1')
parts.append(' ' * int(count))
if child.text:
parts.append(child.text)
if child.tail:
parts.append(child.tail)
lines.append(''.join(parts))
note_text = '\n'.join(lines)
notes.append({'idx': i, 'note': note_text})
return notes
def main():
if len(sys.argv) != 2:
print(f'Usage: {sys.argv[0]} <file.odp or extracted-dir>', file=sys.stderr)
sys.exit(1)
path = sys.argv[1]
if os.path.isdir(path):
content_xml = os.path.join(path, 'content.xml')
base = path.rstrip('/')
elif zipfile.is_zipfile(path):
tmpdir = tempfile.mkdtemp()
with zipfile.ZipFile(path) as zf:
zf.extract('content.xml', tmpdir)
content_xml = os.path.join(tmpdir, 'content.xml')
base = path
else:
print(f'Error: {path} is not an ODP file or extracted directory', file=sys.stderr)
sys.exit(1)
notes = extract_notes(content_xml)
pdfpc = {
'pdfpcFormat': 2,
'pages': notes,
}
outpath = base + '.pdfpc'
with open(outpath, 'w', encoding='utf-8') as f:
json.dump(pdfpc, f, indent=2, ensure_ascii=False)
non_empty = sum(1 for n in notes if n['note'].strip())
print(f'{outpath}: {len(notes)} slides, {non_empty} with notes')
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment