"""Minimalistic EXIF Editor. Commands overview: ls, get, set, del operate with EXIF tags. cget, cset, cdel operate with JSON in UserComment EXIF tag. Usage: mee ls mee get [-k ] mee set -k -v [-o ] mee del -k [-o ] mee cget [-k ] mee cset -k -v [-o ] mee cdel -k [-o ] mee (--help | --version) Options: -o, --output output file. Same as input file by default. -k, --key set EXIF/JSON key. -v, --value EXIF/JSON key value. --help print this message and exit. --version print version and exit. """ __version__ = "0.1.0" import re import sys import json from tempfile import NamedTemporaryFile from typing import Optional from pathlib import Path from docopt import docopt from exif import Image def open_image(infile: Path) -> Image: with open(infile, "rb") as in_img: return Image(in_img) def commit_image(img: Image, infile: Path = None, outfile: Path = None) -> None: if not outfile: with NamedTemporaryFile(suffix="~") as out_tmp, open(infile, "wb") as out_img: out_tmp.write(img.get_file()) out_tmp.seek(0) out_img.write(out_tmp.read()) else: with open(outfile, "wb") as out_img: out_img.write(img.get_file()) def get_exif(key: str, infile: Path, echo: bool = True) -> None: image = open_image(infile) value = image.get(key, "n/a") if echo: print(f"{key}: {value}") else: return value def ls_exif(infile: Path) -> None: image = open_image(infile) tags = image.list_all() print(tags) def set_exif(key: str, value: str, infile: Path, outfile: Path = None) -> None: image = open_image(infile) image.set(key, value) commit_image(image, infile, outfile) def del_exif(key: str, infile: Path, outfile: Path = None) -> None: image = open_image(infile) image.delete(key) commit_image(image, infile, outfile) def get_user_comment(infile: Path) -> dict: user_comment = get_exif("user_comment", infile, echo=False) try: return json.loads(user_comment) except (TypeError, json.JSONDecodeError): return {} def get_json(infile: Path, key: str = None) -> None: user_comment = get_user_comment(infile) if key: try: print(user_comment[key]) except KeyError as e: sys.exit(f"No key: {e}") else: print(json.dumps(user_comment, indent=4, sort_keys=True)) def mod_json(key: str, value: str, infile: Path, outfile: Path = None, delete: bool = False) -> None: user_comment = get_user_comment(infile) if delete: del user_comment[key] else: user_comment[key] = value try: del_exif("user_comment", infile, outfile) except AttributeError: pass set_exif("user_comment", json.dumps(user_comment), infile, outfile) def cli() -> None: args = docopt(__doc__, version=__version__) key = args["--key"] value = args["--value"] infile = args[''] outfile = args['--output'] if args['ls']: ls_exif(infile) if args['get']: if not key: ls_exif(infile) else: get_exif(key, infile) if args["set"]: set_exif(key, value, infile, outfile) if args['del']: del_exif(key, infile, outfile) if args['cget']: get_json(infile, key) if args["cset"]: mod_json(key, value, infile, outfile) if args['cdel']: mod_json(key, None, infile, outfile, delete=True) if __name__ == "__main__": cli()