147 lines
3.7 KiB
Python
147 lines
3.7 KiB
Python
"""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 <file>
|
|
mee get [-k <key>] <file>
|
|
mee set -k <key> -v <value> <file> [-o <file>]
|
|
mee del -k <key> <file> [-o <file>]
|
|
mee cget [-k <key>] <file>
|
|
mee cset -k <key> -v <value> <file> [-o <file>]
|
|
mee cdel -k <key> <file> [-o <file>]
|
|
mee (--help | --version)
|
|
|
|
Options:
|
|
-o, --output <file> output file. Same as input file by default.
|
|
-k, --key <key> set EXIF/JSON key.
|
|
-v, --value <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['<file>']
|
|
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()
|