168 lines
5.7 KiB
Python
Executable File
168 lines
5.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
'''
|
|
Export existing icns files or compose new ones.
|
|
'''
|
|
import os # path, mkdir
|
|
import icnsutil
|
|
from sys import stderr
|
|
from argparse import ArgumentParser, ArgumentTypeError, RawTextHelpFormatter
|
|
|
|
__version__ = icnsutil.__version__
|
|
|
|
|
|
def cli_extract(args):
|
|
''' Read and extract contents of icns file(s). '''
|
|
multiple = len(args.file) > 1
|
|
for i, fname in enumerate(args.file):
|
|
# PathExist ensures that all files and directories exist
|
|
out = args.export_dir
|
|
if out and multiple:
|
|
out = os.path.join(out, str(i))
|
|
os.makedirs(out, exist_ok=True)
|
|
|
|
pred = 'png' if args.png_only else None
|
|
icnsutil.IcnsFile(fname).export(
|
|
out, allowed_ext=pred, recursive=args.recursive,
|
|
convert_png=args.convert, key_suffix=args.keys)
|
|
|
|
|
|
def cli_compose(args):
|
|
''' Create new icns file from provided image files. '''
|
|
dest = args.target
|
|
if not os.path.splitext(dest)[1]:
|
|
dest += '.icns' # for the lazy people
|
|
if not args.force and os.path.exists(dest):
|
|
print(f'File "{dest}" already exists. Force overwrite with -f.',
|
|
file=stderr)
|
|
return 1
|
|
img = icnsutil.IcnsFile()
|
|
for x in args.source:
|
|
img.add_media(file=x)
|
|
img.write(dest, toc=not args.no_toc)
|
|
|
|
|
|
def cli_print(args):
|
|
''' Print contents of icns file(s). '''
|
|
for fname in args.file:
|
|
print('File:', fname)
|
|
print(icnsutil.IcnsFile.description(
|
|
fname, verbose=args.verbose, indent=2))
|
|
|
|
|
|
def cli_verify(args):
|
|
''' Test if icns file is valid. '''
|
|
for fname in args.file:
|
|
is_valid = True
|
|
if not args.quiet:
|
|
print('File:', fname)
|
|
is_valid = None
|
|
for issue in icnsutil.IcnsFile.verify(fname):
|
|
if is_valid:
|
|
print('File:', fname)
|
|
is_valid = False
|
|
print(' ', issue)
|
|
if not args.quiet and is_valid is not False:
|
|
print('OK')
|
|
|
|
|
|
def main():
|
|
class PathExist:
|
|
def __init__(self, kind=None):
|
|
self.kind = kind
|
|
|
|
def __call__(self, path):
|
|
if not os.path.exists(path) or \
|
|
self.kind == 'f' and not os.path.isfile(path) or \
|
|
self.kind == 'd' and not os.path.isdir(path):
|
|
raise ArgumentTypeError('Does not exist "{}"'.format(path))
|
|
return path
|
|
|
|
# Args Parser
|
|
parser = ArgumentParser(description=__doc__,
|
|
formatter_class=RawTextHelpFormatter)
|
|
parser.set_defaults(func=lambda _: parser.print_help(stderr))
|
|
parser.add_argument(
|
|
'-v', '--version', action='version', version='icnsutil ' + __version__)
|
|
sub_parser = parser.add_subparsers(metavar='command')
|
|
|
|
# Extract
|
|
cmd = sub_parser.add_parser(
|
|
'extract', aliases=['e'], formatter_class=RawTextHelpFormatter,
|
|
help=cli_extract.__doc__, description=cli_extract.__doc__.strip())
|
|
cmd.add_argument(
|
|
'-r', '--recursive', action='store_true',
|
|
help='extract nested icns files as well')
|
|
cmd.add_argument(
|
|
'-o', '--export-dir', type=PathExist('d'), metavar='DIR',
|
|
help='set custom export directory')
|
|
cmd.add_argument(
|
|
'-k', '--keys', action='store_true',
|
|
help='use icns key as filenam')
|
|
cmd.add_argument(
|
|
'-c', '--convert', action='store_true',
|
|
help='convert ARGB and RGB images to PNG')
|
|
cmd.add_argument(
|
|
'--png-only', action='store_true',
|
|
help='do not extract ARGB, binary, and meta files')
|
|
cmd.add_argument(
|
|
'file', nargs='+', type=PathExist('f'), metavar='FILE',
|
|
help='One or more .icns files.')
|
|
cmd.set_defaults(func=cli_extract)
|
|
|
|
# Compose
|
|
cmd = sub_parser.add_parser(
|
|
'compose', aliases=['c'], formatter_class=RawTextHelpFormatter,
|
|
help=cli_compose.__doc__, description=cli_compose.__doc__.strip())
|
|
cmd.add_argument(
|
|
'-f', '--force', action='store_true',
|
|
help='force overwrite output file')
|
|
cmd.add_argument(
|
|
'--no-toc', action='store_true',
|
|
help='do not write table of contents to file')
|
|
cmd.add_argument(
|
|
'target', type=str, metavar='destination',
|
|
help='Output file for newly created icns file.')
|
|
cmd.add_argument(
|
|
'source', nargs='+', type=PathExist('f'), metavar='src',
|
|
help='One or more media files: png, argb, plist, icns.')
|
|
cmd.set_defaults(func=cli_compose)
|
|
cmd.epilog = f'''
|
|
Notes:
|
|
- TOC is optional but only a few bytes long (8b per media entry).
|
|
- Icon dimensions are read directly from file.
|
|
- Filename suffix "@2x.png" or "@2x.jp2" sets the retina flag.
|
|
- Use one of these suffixes to automatically assign icns files:
|
|
{', '.join(f'{x.name.lower()}.icns' for x in icnsutil.IcnsType.Role)}
|
|
'''
|
|
|
|
# Print
|
|
cmd = sub_parser.add_parser(
|
|
'print', aliases=['p'], formatter_class=RawTextHelpFormatter,
|
|
help=cli_print.__doc__, description=cli_print.__doc__.strip())
|
|
cmd.add_argument(
|
|
'-v', '--verbose', action='store_true',
|
|
help='print all keys with offsets and sizes')
|
|
cmd.add_argument(
|
|
'file', nargs='+', type=PathExist('f'), metavar='FILE',
|
|
help='One or more .icns files.')
|
|
cmd.set_defaults(func=cli_print)
|
|
|
|
# Verify
|
|
cmd = sub_parser.add_parser(
|
|
'test', aliases=['t'], formatter_class=RawTextHelpFormatter,
|
|
help=cli_verify.__doc__, description=cli_verify.__doc__.strip())
|
|
cmd.add_argument(
|
|
'-q', '--quiet', action='store_true',
|
|
help='do not print OK results')
|
|
cmd.add_argument(
|
|
'file', nargs='+', type=PathExist('f'), metavar='FILE',
|
|
help='One or more .icns files.')
|
|
cmd.set_defaults(func=cli_verify)
|
|
|
|
args = parser.parse_args()
|
|
args.func(args)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|