-
-
Save dreness/b5ebd1ebff092b124d343164c6217a98 to your computer and use it in GitHub Desktop.
| #!/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() |
Hi, sorry for the slow reply, @MikeiLL. I’ve never tried manually editing the user shapes library (outside of Keynote, I mean), but I’m pretty sure it is designed to support “external” updates, as the data is synced with icloud - this is how shapes you add on a Mac are visible in iOS, for example.
Perhaps you noticed the scare-quotes above. I could imagine that Apple might take steps to prevent manual edits to this data from being recognized as valid to Keynote and / or iCloud (eg a signed checksum wrapped in a public key trusted by keynote / iCloud), but there’s only one way to find out :)
(a few minutes later)
(a few minutes later)
... actually SVG2Keynote-gui is busted on Apple Silicon. There are several problems to work through, but according to this comment, it seems possible.
Nice gist, @dreness. I'm actually trying to go the other way. Do you happen to know if Keynote will honor externally made updates made to
TSKPATH?