Skip to content

Instantly share code, notes, and snippets.

@Victorious3
Created June 28, 2018 20:48
Show Gist options
  • Save Victorious3/34b4720b3ad948722b27a6b9114e700c to your computer and use it in GitHub Desktop.
Save Victorious3/34b4720b3ad948722b27a6b9114e700c to your computer and use it in GitHub Desktop.
SAI File Converter
# sai_converter.py by VicNightfall - https://twitter.com/VicNightfall
#
# ---------------------------------- README: --------------------------------------------
# First you need to install python, if you haven't done that already.
# You can get it from here: https://www.python.org/downloads/ (Just get the newest release)
#
# You also need to install pywinauto:
# pip install pywinauto
#
# (inside a command prompt, see steps below)
#
# It needs to be run as Administrator for some reason or
# else the import on pywinauto fails, so make sure you create
# an elevated prompt and run it from there:
#
# type <Win> powershell <Ctrl+Shift+Enter>
#
# From here you first have to navigate to wherever you saved this file,
# this might be your Downloads folder. I recommend moving it to where your pictures are,
# that makes specifying the other arguments a lot easier.
#
# In my case:
#
# cd C:\Users\Vic\Downloads
#
# Once you are inside here you can run the script with
#
# py sai_converter.py -h
#
# This will show all the options for it.
#
# You can contact me on twitter if you have any questions.
# Tested with SAI 1.2.5, other versions might not work.
import ctypes, sys
if not ctypes.windll.shell32.IsUserAnAdmin(): # Requires elevated priviliges
print("You have to run this script with Administrator privileges!")
exit(-1)
import time, os, argparse
from pywinauto import application, keyboard, findwindows
from glob import glob
extensions = ["psd", "bmp", "jpg", "png", "tga"]
scales = ["6.25%", "12.5%", "25%", "50%", "100%", "200%", "400%", "800%", "1600%"]
parser = argparse.ArgumentParser(description = "Convert .sai files into another program")
parser.add_argument("file", type = str, nargs = '+', help = "List of files to convert")
parser.add_argument("-e", "--extension", choices = extensions, help = "File extension to convert to", required = True)
parser.add_argument("-o", "--out", type = str, help = "Output directory", default = os.getcwd())
parser.add_argument("--overwrite", action = "store_true", help = "Overwrite already existing files")
parser.add_argument("--sai", help = "Path to sai executable")
png_opt = parser.add_argument_group("png")
png_opt.add_argument("--no-opacity", action = "store_true", help = "Export png files without opacity")
jpg_opt = parser.add_argument_group("jpg")
jpg_opt.add_argument("--scale", type = str, choices = scales, help = "Output image scaling", default = "100%")
jpg_opt.add_argument("--compressed", action = "store_true", help = "Enable jpg compression (Lower file size and lower quality)")
jpg_opt.add_argument("--quality", type = int, choices = range(0, 101), metavar="[0-100]", help = "Quality, in percent", default = 100)
args = parser.parse_args()
if args.sai is None:
sai_exec = "C:\\Program Files (x86)\\PaintToolSAI\\sai.exe"
if not os.path.isfile(sai_exec):
sai_exec = "C:\\PaintToolSAI\\sai.exe"
if not os.path.isfile(sai_exec):
print("Can't find the sai executable, please specify it with --sai")
exit(-1)
else:
sai_exec = args.sai
if not os.path.isfile(sai_exec):
print("--sai: Not a valid file")
exit(-1)
extension = extensions.index(args.extension) + 1 # This is how often we need to press down
extension = "{VK_DOWN}" * extension # So make a string from it
if not os.path.isdir(args.out):
print("--out: Not a valid path name")
exit(-1)
args.out = os.path.abspath(args.out)
files = []
for fstr in args.file:
for filename in glob(fstr):
if not (filename.endswith(".sai") or filename.endswith(".sai2")): continue # Skip all non sai files
files.append(os.path.abspath(filename))
if not files:
print("No files specified!")
app = application.Application()
try:
app.connect(path = sai_exec, timeout = 0)
print("SAI is already running! Please close it before you run the converter.")
exit(-1)
except application.ProcessNotFoundError:
pass
for file in files:
if not os.path.exists(file):
print("Skipping: " + file + " - Files does not exist")
continue
app.start(sai_exec + " " + file)
time.sleep(0.5)
sai = app.window(title_re="SAI")
time.sleep(0.5)
sai.type_keys("%F%E" + extension + "{ENTER}")
time.sleep(0.5)
save_dialog = app["Save Canvas"] # Save Canvas Dialog
save_dialog[2].click()
save_dialog[2].type_keys(args.out, with_spaces = True, pause = 0)
save_dialog[2].type_keys("{ENTER}")
# save_dialog[15] - File name
save_dialog.Save.click()
if save_dialog.exists(): # Popup
if args.overwrite: app.top_window()["&Yes"].click()
else:
app.top_window()["&No"].click()
app.kill()
print("Skipping: " + file + " - File already exists")
continue
# Additional arguments here
if args.extension == "png":
png_options = app.top_window()
if args.no_opacity:
png_options.child_window(title_re="24").click()
else:
png_options.child_window(title_re="32").click()
png_options["OK"].click()
elif args.extension == "jpg":
jpg_options = app.top_window()
# Compression switch
button_height = (jpg_options[1].Rectangle().height() - 2) // 2
if args.compressed:
jpg_options[1].click(button = 'left', coords = (0, button_height + button_height // 2))
else:
jpg_options[1].click(button = 'left', coords = (0, button_height - button_height // 2))
# Scale switch
scale = scales.index(args.scale)
button_height = (jpg_options[2].Rectangle().height() - 2) // 9
jpg_options[2].click(button = 'left', coords = (0, button_height * scale + button_height // 2))
# Quality switch
width = jpg_options[6].Rectangle().width() - 2
print(args.quality / 100 * width, width)
jpg_options[6].click(button = 'left', coords = (int(args.quality / 100 * width), 0))
jpg_options["OK"].click()
sai.wait("enabled", timeout = 30) # Wait for save to finish
print("Converted: " + file + " -> " + args.out + "\\" + os.path.splitext(os.path.basename(file))[0] + "." + args.extension)
app.kill()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment