Sideband/sbapp/assets/icons_macos.py

316 lines
9.2 KiB
Python
Raw Permalink Normal View History

2022-10-03 12:27:48 -06:00
import sys
import pathlib
import argparse
import subprocess
import platform
__version_info__ = (1, 2, 0)
__version__ = ".".join(map(str, __version_info__))
__copyright__: str = f"Copyright (C) 2018-2022 Declaration of VAR"
# default iconset files extension, gets the value from original image
ext = ".png"
# programs names
iconutilProgram = "iconutil"
magickProgram = "magick"
sipsProgram = "sips"
class IconParameters():
width = 0
scale = 1
def __init__(self, width, scale):
self.width = width
self.scale = scale
def getIconName(self):
global ext
scaleString = "" if self.scale == 1 else f"@{self.scale}x"
return f"icon_{self.width}x{self.width}{scaleString}{ext}"
def checkProgramInPath(programName, errorMsg=None):
checkResult = subprocess.run(
["which", programName],
capture_output=True,
text=True
)
errorMsgString = "" if errorMsg is None else f". {errorMsg}"
if checkResult.returncode != 0:
raise SystemExit(
f"[ERROR] Couldn't find {programName} in your PATH{errorMsgString}"
)
else:
print(f"Found {programName}: {checkResult.stdout.strip()}")
def generateImageConvertingCommand(forSips, originalPicture, ip, iconsetDir):
if not forSips:
return [
magickProgram,
"convert",
originalPicture,
"-resize",
str(ip.width * ip.scale),
iconsetDir / ip.getIconName()
]
else:
return [
sipsProgram,
"-z",
str(ip.width * ip.scale),
str(ip.width * ip.scale),
originalPicture,
"--out",
iconsetDir / ip.getIconName()
]
def main():
global ext
argParser = argparse.ArgumentParser(
prog="generate-iconset",
description=" ".join((
f"%(prog)s\n{__copyright__}\nGenerate",
"an iconset for a Mac OS application",
f"using {iconutilProgram} tool"
)),
formatter_class=argparse.RawDescriptionHelpFormatter,
allow_abbrev=False
)
argParser.add_argument(
"--version",
action="version",
version=f"%(prog)s {__version__}"
)
argParser.add_argument(
"image",
metavar="/path/image.png",
help="path to the original image"
)
argParser.add_argument(
"--out",
metavar="/path/out/",
help=" ".join((
"path to the output folder, where to put resulting",
".icns file (default: same folder as original image)"
))
)
argParser.add_argument(
"--use-sips",
action='store_true',
help=f"use {sipsProgram} instead of ImageMagick (default: %(default)s)"
)
argParser.add_argument(
"--force-png",
action='store_true',
help=" ".join((
"force non-.png original image to be converted",
"to .png (default: %(default)s)"
))
)
argParser.add_argument(
"--ignore-non-mac",
action='store_true',
help=" ".join((
"allows to run the script on any OS, not only on Mac OS",
"(default: %(default)s)"
))
)
# argParser.add_argument(
# "--delete-tmp-iconset",
# action='store_true',
# help=" ".join((
# "delete temporary iconset directory,",
# "if it already exists (default: %(default)s)"
# ))
# )
cliArgs = argParser.parse_args()
# print(cliArgs)
if platform.system() != "Darwin":
if not cliArgs.ignore_non_mac:
raise SystemExit(
" ".join((
"[ERROR] The script is meant to be executed",
f"on Mac OS only, as {iconutilProgram} tool is only available",
"there. You can ignore this condition",
"with --ignore-non-mac"
))
)
else:
print(
" ".join((
"[WARNING] You are running the script not on Mac OS,",
"so it is likely to fail,",
f"unless you have {iconutilProgram} tool installed"
))
)
checkProgramInPath(iconutilProgram)
if not cliArgs.use_sips:
print("Will use ImageMagick for converting the original image")
checkProgramInPath(
magickProgram,
" ".join((
"Perhaps, you don't have it installed?",
f"You can also use {sipsProgram} tool instead",
"with --use-sips"
))
)
else:
print(f"Will use {sipsProgram} for converting the original image")
print(
" ".join((
"[WARNING] ImageMagick provides better quality results,",
f"so do consider using it instead of {sipsProgram}. More details:",
"https://decovar.dev/blog/2019/12/12/imagemagick-vs-sips-resize/"
))
)
checkProgramInPath(sipsProgram)
print()
originalPicture = pathlib.Path(cliArgs.image)
if not (originalPicture.is_file()):
raise SystemExit(
f"[ERROR] There is no such image file: {cliArgs.image}"
)
print(f"Original image: {originalPicture}")
fname = pathlib.Path(originalPicture).stem
ext = pathlib.Path(originalPicture).suffix
if ext != ".png":
if not cliArgs.force_png:
print(
" ".join((
"[WARNING] Original image extension is not .png,",
f"{iconutilProgram} is likely to fail,",
"pass --force-png to avoid that"
))
)
else:
ext = ".png"
# destination path for output
destDir = (
pathlib.Path(originalPicture).parent
if cliArgs.out is None
else pathlib.Path(cliArgs.out)
)
if not (destDir.is_dir()):
try:
destDir.mkdir(parents=True)
except Exception as ex:
raise SystemExit(
" ".join((
"[ERROR] The specified output folder doesn't exist",
f"and could not be created: {cliArgs.out}"
))
)
# path to resulting .icns file
resultingIconset = destDir / f"{fname}.icns"
# path to temporary iconset folder
iconsetDir = pathlib.Path(destDir / f"{fname}.iconset")
if not (iconsetDir.is_dir()):
try:
iconsetDir.mkdir()
except Exception as ex:
raise SystemExit(
" ".join((
"[ERROR] Could not create temporary",
f"iconset folder: {iconsetDir}"
))
)
else:
if False: # cliArgs.delete_tmp_iconset:
# not the best idea to let script delete files on disk
print("[DEBUG] Deleting temporary iconset folder")
else:
raise SystemExit(
" ".join((
f"[ERROR] Temporary iconset directory ({iconsetDir})",
"already exists, you need to",
"delete it first"
# "either delete it manually",
# "or use --delete-tmp-iconset"
))
)
# https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/app-icon#app-icon-sizes
ListOfIconParameters = [
IconParameters(16, 1),
IconParameters(16, 2),
IconParameters(32, 1),
IconParameters(32, 2),
IconParameters(128, 1),
IconParameters(128, 2),
IconParameters(256, 1),
IconParameters(256, 2),
IconParameters(512, 1),
IconParameters(512, 2)
]
print("Converting images for iconset")
# generate iconset
currentImage = 0
for ip in ListOfIconParameters:
currentImage += 1
convertingResult = subprocess.run(
generateImageConvertingCommand(
cliArgs.use_sips,
originalPicture,
ip,
iconsetDir
),
capture_output=True,
text=True
)
if convertingResult.returncode != 0:
raise SystemExit(
f"[ERROR] Conversion failed. {convertingResult.stderr.strip()}"
)
else:
print(f"{currentImage}/{len(ListOfIconParameters)}...")
# print(f"Generated {ip.getIconName()}")
print("\nGenerating .icns file...")
# convert iconset folder to .icns file
iconutilResult = subprocess.run(
[
iconutilProgram,
"-c",
"icns",
iconsetDir,
"-o",
resultingIconset
],
capture_output=True,
text=True
)
if iconutilResult.returncode != 0:
raise SystemExit(
" ".join((
f"[ERROR] {iconutilProgram} could not generate",
f"an iconset. {iconutilResult.stderr.strip()}"
))
)
else:
print(
" ".join((
"[SUCCESS] An iconset was successfully",
f"generated to {resultingIconset}"
))
)
raise SystemExit(0)
if __name__ == '__main__':
main()