Files

168 lines
4.6 KiB
Python

#!/usr/bin/env python3
#
#
# Library to extract Exif information from digital camera image files.
# https://github.com/ianare/exif-py
#
#
# Copyright (c) 2002-2007 Gene Cash
# Copyright (c) 2007-2025 Ianaré Sévi and contributors
#
# See LICENSE.txt file for licensing information
# See ChangeLog.rst file for all contributors and changes
#
"""
Runs Exif tag extraction in command line.
"""
import argparse
import sys
import timeit
from exifread import __version__, exif_log, process_file
from exifread.core.exceptions import ExifError
from exifread.tags.fields import FIELD_DEFINITIONS
logger = exif_log.get_logger()
def get_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
prog="EXIF.py", description="Extract EXIF information from digital image files."
)
parser.add_argument(
"files",
metavar="FILE",
type=str,
nargs="+",
help="files to process",
)
parser.add_argument(
"-v",
"--version",
action="version",
version="EXIF.py Version %s on Python%s" % (__version__, sys.version_info[0]),
help="Display version information and exit",
)
parser.add_argument(
"-q",
"--quick",
action="store_false",
dest="detailed",
help="Do not process MakerNotes and do not extract thumbnails",
)
parser.add_argument(
"-t",
"--tag",
type=str,
dest="stop_tag",
help="Stop processing when this tag is retrieved.",
)
parser.add_argument(
"-s",
"--strict",
action="store_true",
dest="strict",
help="Run in strict mode (stop on errors).",
)
parser.add_argument(
"-b",
"--builtin",
action="store_true",
dest="builtin_types",
help="Convert IfdTag values to built-in Python variable types",
)
parser.add_argument(
"-d",
"--debug",
action="store_true",
dest="debug",
help="Run in debug mode (display extra info).",
)
parser.add_argument(
"-c",
"--color",
action="store_true",
dest="color",
help="Output in color (only works with debug on POSIX).",
)
args = parser.parse_args()
return args
def run_cli(args: argparse.Namespace) -> None:
"""Extract tags based on options (args)."""
exif_log.setup_logger(args.debug, args.color)
# output info for each file
for filename in args.files:
# avoid errors when printing to console
escaped_fn = filename.encode(
sys.getfilesystemencoding(), "surrogateescape"
).decode()
file_start = timeit.default_timer()
try:
with open(escaped_fn, "rb") as img_file:
logger.info("Opening: %s", escaped_fn)
tag_start = timeit.default_timer()
# get the tags
data = process_file(
img_file,
stop_tag=args.stop_tag,
details=args.detailed,
strict=args.strict,
debug=args.debug,
extract_thumbnail=args.detailed,
builtin_types=args.builtin_types,
)
tag_stop = timeit.default_timer()
except IOError:
logger.error("'%s' is unreadable", escaped_fn)
continue
if not data:
logger.warning("No EXIF information found")
print()
continue
if "JPEGThumbnail" in data:
logger.info("File has JPEG thumbnail")
del data["JPEGThumbnail"]
if "TIFFThumbnail" in data:
logger.info("File has TIFF thumbnail")
del data["TIFFThumbnail"]
for field in sorted(data):
value = data[field]
try:
if args.builtin_types:
logger.info("%s (%s): %r", field, type(value).__name__, value)
else:
logger.info(
"%s (%s): %s",
field,
FIELD_DEFINITIONS[value.field_type][1],
value.printable,
)
except (ExifError, ValueError):
logger.error("%s: %s", field, str(value))
file_stop = timeit.default_timer()
logger.debug("Tags processed in %s seconds", tag_stop - tag_start)
logger.debug("File processed in %s seconds", file_stop - file_start)
print()
def main() -> None:
run_cli(get_args())
if __name__ == "__main__":
main()