Skip to content

Instantly share code, notes, and snippets.

@dreness
Created July 22, 2020 20:29
Show Gist options
  • Save dreness/6a23ef1d7183c7b98cee3978c9b5facc to your computer and use it in GitHub Desktop.
Save dreness/6a23ef1d7183c7b98cee3978c9b5facc to your computer and use it in GitHub Desktop.
Export Keynote shapes to SVG
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import json # Native JSON Library
import argparse # Native Argument parser Library
# This is a slightly updated version of a script found on ye cyberwebs to
# add batch conversion support. Just don't specify an image ID to export the world.
# ==============================#
# IMPORTANT SCRIPT INFORMATION #
# ==============================#
SCRIPT_VERSION = "%(prog)s 1.0 (20w22a)"
SCRIPT_CREATOR = "donemanuelvuckovic@gmail.com"
SCRIPT_DESCRIPTION = """iShapeParser is a script to parse (and convert) () Apple's Keynote shape_library into useful svg files.
Usage:
To view all possible shapes:
python3 iShapeParser.py
To export the found shape using default svg options in the same dir where the script is with the name of the shape:
python3 iShapeParser.py -i <ID> -gs
I take no responsibility if something breaks while this script is being used. There is no warranty, you can use it as is etc.
Also feel free to copy, edit and share this script as long as you attribute me :)
"""
SCRIPT_EPILOG = f"© Don Vuckovic 2020. Contact {SCRIPT_CREATOR} for help!"
# ===================================#
# A R G U M E N T P A R S E R #
# ===================================#
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description=SCRIPT_DESCRIPTION,
epilog=SCRIPT_EPILOG,
)
parser.add_argument("--version", action="version", version=SCRIPT_VERSION)
parser.add_argument(
"-d",
"--debug",
help="Use this for debugging, max. log level = 5",
action="count",
default=0,
)
parser.add_argument(
"-f",
"--file",
help="Which file to use for json shape extracting",
default="shape_library.json",
)
parser.add_argument(
"-i", "--id", help="Specify if you want a specific shape ID to be extracted only"
)
parser.add_argument(
"-g", "--generate", help="When active a svg will be generated", action="store_true"
)
parser.add_argument(
"-s",
"--save",
help="If a svg was generated, use this to save it to a .svg file",
action="store_true",
)
parser.add_argument("-p", "--path", help="Change the path of the save directory")
parser.add_argument("-n", "--name", help="Change the name of the exported svg file")
# SVG SPECIFIC VALUES (Change at own risk)
parser.add_argument(
"-w",
"--width",
help="Specify the width of the svg viewBox (Changing this can break the svg!) Default: 700.",
default=700,
)
parser.add_argument(
"-t",
"--height", # t is for tallness as h is already used for help!
help="Specify the width of the svg viewBox (Changing this can break the svg!) Default: 500.",
default=500,
)
parser.add_argument(
"-x",
"--xpos",
help="Specify the x of the svg file (Changing this can break the svg!) Default: 0.",
default=0,
)
parser.add_argument(
"-y",
"--ypos",
help="Specify the y of the svg file (Changing this can break the svg!) Default: 0.",
default=0,
)
parser.add_argument(
"-a",
"--minx",
help="Specify the min-x of the svg viewBox (Changing this can break the svg!) Default: 50.",
default=50,
)
parser.add_argument(
"-b",
"--miny",
help="Specify the min-y of the svg viewBox (Changing this can break the svg!) Default: 50.",
default=50,
)
parser.add_argument(
"-l", "--layer", help="Specify the layer name. Default: Layer_1.", default="Layer_1"
)
args = parser.parse_args()
# ===================#
# F u n c t i o n s #
# ===================#
# Simple function to print according to the debug level
def log(msg, level=3):
if args.debug >= level:
print(f"LEVEL_{level}: {msg}")
# Simple function to throw an error and exit
def throw(msg):
raise Exception(msg) from None
sys.exit()
def translateKeynoteVectorToSVG(SHAPEDATA, args):
svg = f"""<?xml version="1.0" encoding="utf-8"?>
<!-- Generated SVG File by Don Vuckovic's iShapeParser.py! Path taken from () Apple's Keynote shape_library.json -->
<svg id="{args.layer}" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="{args.xpos}px" y="{args.ypos}px"
viewBox="{args.minx} {args.miny} {args.width} {args.height}" xml:space="preserve">
<g>
<path d="
{SHAPEDATA}
"/>
</g>
</svg>"""
return svg
def saveSVG(svg, SHAPENAME, args):
savedir = ""
savename = SHAPENAME
if args.path is not None:
savedir = args.path + "/"
if args.name is not None:
savename = args.name
# some of the localized names may contain special characters...
savename = savename.replace("/", "-")
outpath = f"{savedir}{savename}.svg"
print(f"Creating svg file at {outpath}")
f = open(f"{outpath}", "w")
f.write(svg)
f.close()
print(f"Shape successfully exported to {outpath}.svg")
# ========================================================================================================#
# SCRIPT STARTS HERE - SCRIPT STARTS HERE - SCRIPT STARTS HERE - SCRIPT STARTS HERE - SCRIPT STARTS HERE #
# ========================================================================================================#
log(f"DEBUG LVL = {args.debug}", 1)
log(f"File to use = {args.file}", 1)
# Try to open the json file, exit on fail
try:
with open(args.file, "r") as read_file:
log("LOADING FILE...", 1)
data = json.load(read_file)
log("FILE LOADED!")
except:
throw("Unable to open the provided file!")
# Check the versionID, do this to make sure that you most probabbly have the correct file!
try:
versionid = data["versionID"]
except:
throw("No versionID found within the provided file! Wrong format!")
# Check if the versionID is the same as the one when the script was initially created!
# For now do this only to show a warning in case Apple changes formatting.
# TODO: save this to a variable, and if the script fails warn that it might be due to a version mismatch!
if data["versionID"] != "S3988C2995":
print(
"Version ID not the same as expected, please report this to %s" % SCRIPT_CREATOR
)
# If the shape ID is provided, get it's values
if args.id is not None:
# Try to get the shape name, exit on fail
try:
SHAPENAME = data["shapesByID"][args.id]["localizationKey"]
except:
throw("Shape ID probabbly doesn't exist!")
# Try to get the shape data, exit on fail
try:
SHAPEDATA = data["shapesByID"][args.id]["shapePath"]
except:
throw("Shape ID probabbly doesn't exist!")
# Print the shape data
print(
f"""
NAME = {SHAPENAME}
PATH
------------------------------------------------
{SHAPEDATA}
"""
)
# If the generate argument is passed, generate the SVG format data
if args.generate:
svg = translateKeynoteVectorToSVG(SHAPEDATA, args)
# Print the generated SVG format data
print("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -")
print(svg)
print("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -")
# If the save argument is passed, save the generated SVG format data into the SVG file
if args.save:
saveSVG(svg, SHAPENAME, args)
# Save the SVG as a .svg file, exit on fail
# If the save argument is passed, but no svg has been generated explain the impossibility
elif args.save:
print(
"Please use -g/--generate with -s/--save as well to generate the file to save first!"
)
# If no shape id is provided make a list of all possible shapes
else:
# Try to get the shape list, exit on fail
try:
SHAPELIST = data["shapesByID"].keys()
except:
throw(
"I'm not even sure how you got here. Please report this to %s"
% SCRIPT_CREATOR
)
SHAPECOUNT = len(SHAPELIST)
# Print all shapes found
for key in SHAPELIST:
print(f"""{key} = {data["shapesByID"][key]["localizationKey"]}""")
print(f"Found {SHAPECOUNT} shapes.")
# If the generate argument is passed, generate everything
if args.generate:
print("Generating everything!")
for shapeID in SHAPELIST:
SHAPEDATA = data["shapesByID"][shapeID]["shapePath"]
SHAPENAME = data["shapesByID"][shapeID]["localizationKey"]
svg = translateKeynoteVectorToSVG(SHAPEDATA, args)
saveSVG(svg, SHAPENAME, args)
# If the save argument is passed, but no ID has been provided explain the impossibility
if args.save:
print(
"Unable to save a svg file without the -i/--id and -g/--generate arguments!"
)
# The end :)
@dreness
Copy link
Author

dreness commented Sep 26, 2021

Here's a better one that exports all built-in shapes as well as anything saved to the "my shapes" collection : https://gist.github.com/dreness/b5ebd1ebff092b124d343164c6217a98

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment