init
This commit is contained in:
commit
3a16af64d8
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
dist/
|
48
README.md
Normal file
48
README.md
Normal file
@ -0,0 +1,48 @@
|
||||
mee
|
||||
===
|
||||
|
||||
Minimalistic EXIF Editor.
|
||||
|
||||
MEE allows you to get, remove and change EXIF tags in images.
|
||||
|
||||
In addition to this, support for custom tags is implemented here. Custom tags are written in JSON format to the UserComment tag. MEE can operate with them in the same way as with ordinary tags. Be aware of the 64 kilobytes limit for the UserComment tag.
|
||||
|
||||
Custom tags entered there can only be interpreted manually or with MEE.
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
No prebuilt package provided. Build it by yourself.
|
||||
|
||||
1. Install `poetry`
|
||||
2. Clone repo and run `poetry build`
|
||||
3. Run `pip install ./dist/*.tar.gz`
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
```
|
||||
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.
|
||||
```
|
||||
|
146
mee.py
Normal file
146
mee.py
Normal file
@ -0,0 +1,146 @@
|
||||
"""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()
|
43
poetry.lock
generated
Normal file
43
poetry.lock
generated
Normal file
@ -0,0 +1,43 @@
|
||||
# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "docopt"
|
||||
version = "0.6.2"
|
||||
description = "Pythonic argument parser, that will make you smile"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "exif"
|
||||
version = "1.6.0"
|
||||
description = "Read and modify image EXIF metadata using Python."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "exif-1.6.0-py3-none-any.whl", hash = "sha256:2d002b5740eb897da43de1e6a8f4ebacf5171b4d0b8bc0182c70aebde1be86d3"},
|
||||
{file = "exif-1.6.0.tar.gz", hash = "sha256:36288d1ffc60030084a04c26f50cc32e19383a36cd234fcfa1fb1c6f698e1d36"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
plum-py = ">=0.5.0,<2.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "plum-py"
|
||||
version = "0.8.6"
|
||||
description = "Pack/Unpack Memory."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "plum_py-0.8.6-py3-none-any.whl", hash = "sha256:c0ce6d03fc4bf09dbd838b520085e9dfb690d1604f3eb7a553fb6331c1155b78"},
|
||||
]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "1d8a15c0ec1aedf4cc7db64b156a95869a294604a356c1fc8be4e8d0e955a501"
|
19
pyproject.toml
Normal file
19
pyproject.toml
Normal file
@ -0,0 +1,19 @@
|
||||
[tool.poetry]
|
||||
name = "mee"
|
||||
version = "0.1.0"
|
||||
description = "Minimalistic EXIF Editor."
|
||||
authors = ["ge"]
|
||||
license = "Unlicense"
|
||||
readme = "README.md"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.10"
|
||||
exif = "^1.6.0"
|
||||
docopt = "^0.6.2"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
mee = 'mee:cli'
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
Loading…
Reference in New Issue
Block a user