Skip to content

Instantly share code, notes, and snippets.

@rybesh
Forked from dreness/keynote_shape_to_svg.py
Created June 28, 2023 15:31
Show Gist options
  • Save rybesh/d4b18645b5711854fc75454159b436c2 to your computer and use it in GitHub Desktop.
Save rybesh/d4b18645b5711854fc75454159b436c2 to your computer and use it in GitHub Desktop.
Convert Keynote shapes (built-in and user-generated) to SVG
#!/usr/bin/env python3
# Instructions:
# 1) install svgpathtools with the command:
# pip install svgpathtools
# 2) Run this script with no arguments:
# python keynote_shape_to_svg.py
from svgpathtools import parse_path, svg2paths, wsvg
import sqlite3
import hashlib
import json
import sys
import os
KeynoteShapesPath = "/Applications/Keynote.app/Contents/Resources/shape_library.json"
TSKPATH = "".join(
[
os.environ.get("HOME"),
"/Library/Containers/com.apple.iWork.Keynote/",
"Data/Library/Application Support/",
"com.apple.iWork.CloudKitStorage/",
"com.apple.iWork.TSKCloudKitPrivateZone.db",
]
)
OUTDIR = f"{os.environ.get('HOME')}/Pictures/keynote_shape_extracts/"
def writeSVG(shape, shapeName, OUTDIR):
if not os.path.isdir(OUTDIR):
try:
os.makedirs(OUTDIR, exist_ok=True)
print(f"Created {OUTDIR}")
except OSError as e:
print(f"Couldn't create output directory at {OUTDIR}!\n{e}")
sys.exit(1)
if shapeName is None or shapeName == "":
shapeName = hashlib.sha1(shape.encode("utf-8")).hexdigest()
wsvg(
parse_path(shape),
["black"],
attributes=[{"fill": "black"}],
filename=f"{OUTDIR}/{shapeName}.svg",
)
def writeShapes(shapeData, OUTDIR):
# shapeData is a list of (shape, shapeName) tuples
wrote = 0
for row in shapeData:
shape = row[0]
shapeName = row[1]
writeSVG(shape, shapeName, OUTDIR)
wrote += 1
print(f"Exported {wrote} keynote shapes as SVGs to {OUTDIR}")
def getUserShapes(TSKPATH=TSKPATH):
con = sqlite3.connect(TSKPATH)
cur = con.cursor()
if cur is None:
print(f"Couldn't access sqlite DB at {TSKPATH}")
sys.exit(1)
else:
print(f"Opened Keynote user library:\n{TSKPATH}")
# The table that holds the user shapes has this schema:
# CREATE TABLE tsduserdefinedshapelibraryshape
# (
# identifier TEXT PRIMARY KEY,
# cloudkitmetadata TEXT,
# needs_first_fetch INTEGER DEFAULT 0,
# tsduserdefinedshapelibrarybezierpathstringkey TEXT,
# tsduserdefinedshapelibrarynamekey TEXT,
# position REAL
# )
Q = """
SELECT
tsduserdefinedshapelibrarybezierpathstringkey,
tsduserdefinedshapelibrarynamekey
FROM
tsduserdefinedshapelibraryshape;
"""
l = list(cur.execute(Q))
cur.close()
return l
def getKeynoteShapes(OUTDIR=OUTDIR, KeynoteShapesPath=KeynoteShapesPath):
d = dict(json.loads(open(KeynoteShapesPath, "r").read()))
outList = []
l = d.get("shapesByID")
for k, v in l.items():
shapeName = v.get("localizationKey")
shape = v.get("shapePath")
# print(shapeName, shape)
outList.append((shape, shapeName))
return outList
writeShapes(outList, OUTDIR)
def main():
print("Looking for built-in shapes...")
writeShapes(getKeynoteShapes(), OUTDIR)
print("Looking for user shapes...")
writeShapes(getUserShapes(), OUTDIR)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment