diff --git a/README.md b/README.md index 0d9fed0..81d6f1c 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ positional arguments: command extract (e) Read and extract contents of icns file(s). compose (c) Create new icns file from provided image files. + update (u) Update existing icns file by inserting or removing media entries. print (p) Print contents of icns file(s). test (t) Test if icns file is valid. convert (img) Convert images between PNG, ARGB, or RGB + alpha mask. @@ -31,16 +32,21 @@ positional arguments: ```sh # extract -icnsutil e ExistingIcon.icns -o ./outdir/ +icnsutil e Existing.icns -o ./outdir/ # compose -icnsutil c NewIcon.icns 16x16.png 16x16@2x.png *.jp2 +icnsutil c New.icns 16x16.png 16x16@2x.png *.jp2 + +# update +icnsutil u Existing.icns -rm toc ic04 ic05 +icnsutil u Existing.icns -set is32=16.rgb dark="dark icon.icns" +icnsutil u Existing.icns -rm dark -set ic04=16.argb -o Updated.icns # print -icnsutil p ExistingIcon.icns +icnsutil p Existing.icns # verify valid format -icnsutil t ExistingIcon.icns +icnsutil t Existing.icns # convert image icnsutil img 1024.png 512@2x.jp2 @@ -57,7 +63,7 @@ icnsutil img png 16.rgb 16.mask import icnsutil # extract -img = icnsutil.IcnsFile('ExistingIcon.icns') +img = icnsutil.IcnsFile('Existing.icns') img.export(out_dir, allowed_ext='png', recursive=True, convert_png=True) @@ -65,7 +71,14 @@ img.export(out_dir, allowed_ext='png', img = icnsutil.IcnsFile() img.add_media(file='16x16.png') img.add_media(file='16x16@2x.png') -img.write('./new-icon.icns', toc=False) +img.write('./new-icon.icns') + +# update +img = icnsutil.IcnsFile('Existing.icns') +img.add_media('icp4', file='16x16.png', force=True) +if img.remove_media('TOC '): + print('table of contents removed') +img.write('Existing.icns', toc=False) # print icnsutil.IcnsFile.description(fname, indent=2) diff --git a/icnsutil/IcnsType.py b/icnsutil/IcnsType.py index f011278..e37104e 100644 --- a/icnsutil/IcnsType.py +++ b/icnsutil/IcnsType.py @@ -213,6 +213,16 @@ def get(key: Media.KeyT) -> Media: raise NotImplementedError('Unsupported icns type "' + str(key) + '"') +def key_from_readable(key: str) -> Media.KeyT: + key_mapping = { + 'dark': b'\xFD\xD9\x2F\xA8', + 'selected': 'slct', + 'template': 'sbtp', + 'toc': 'TOC ', + } # type: Dict[str, Media.KeyT] + return key_mapping.get(key.lower(), key) + + def match_maxsize(total: int, typ: str) -> Media: assert(typ == 'argb' or typ == 'rgb') ret = [x for x in _TYPES.values() if x.is_type(typ) and x.maxsize == total] diff --git a/icnsutil/cli.py b/icnsutil/cli.py index e419741..ba084bc 100755 --- a/icnsutil/cli.py +++ b/icnsutil/cli.py @@ -9,7 +9,7 @@ from argparse import ArgumentParser, ArgumentTypeError, RawTextHelpFormatter from argparse import Namespace as ArgParams if __name__ == '__main__': sys.path[0] = os.path.dirname(sys.path[0]) -from icnsutil import __version__, IcnsFile, ArgbImage +from icnsutil import __version__, IcnsFile, IcnsType, ArgbImage def cli_extract(args: ArgParams) -> None: @@ -44,6 +44,33 @@ def cli_compose(args: ArgParams) -> None: img.write(dest, toc=not args.no_toc) +def cli_update(args: ArgParams) -> None: + ''' Update existing icns file by inserting or removing media entries. ''' + icns = IcnsFile(args.file) + has_changes = False + # remove media + for x in args.rm or []: + has_changes |= icns.remove_media(IcnsType.key_from_readable(x)) + # add media + for key_val in args.set or []: + def fail(): + raise ArgumentTypeError( + 'Expected arg format KEY=FILE - got "{}"'.format(key_val)) + if '=' not in key_val: + fail() + key, val = key_val.split('=', ) + if not val: + fail() + if not os.path.isfile(val): + raise ArgumentTypeError('File does not exist "{}"'.format(val)) + + icns.add_media(IcnsType.key_from_readable(key), file=val, force=True) + has_changes = True + # write file + if has_changes or args.o: + icns.write(args.o or args.file, toc=icns.has_toc()) + + def cli_print(args: ArgParams) -> None: ''' Print contents of icns file(s). ''' for fname in enum_with_stdin(args.file): @@ -171,6 +198,19 @@ Notes: template, selected, dark ''' + # Update + cmd = add_command('update', 'u', cli_update) + cmd.add_argument('file', type=PathExist('f', stdin=True), + metavar='FILE', help='The icns file to be updated.') + cmd.add_argument('-o', '--output', type=str, metavar='OUT_FILE', + help='Choose another destination, dont overwrite input.') + grp = cmd.add_argument_group('action') + grp.add_argument('-rm', type=str, nargs='+', metavar='KEY', + help='Remove media keys from icns file') + grp.add_argument('-set', type=str, nargs='+', metavar='KEY=FILE', + help='Append or replace media in icns file') + cmd.epilog = 'KEY supports names like "dark", "selected", and "template"' + # Print cmd = add_command('print', 'p', cli_print) cmd.add_argument('-v', '--verbose', action='store_true',