Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@AdamDimech
Last active June 23, 2022 10:29
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AdamDimech/6c83e43c1a70a82e10778b279b3917e5 to your computer and use it in GitHub Desktop.
Save AdamDimech/6c83e43c1a70a82e10778b279b3917e5 to your computer and use it in GitHub Desktop.
A Python script for displaying EXIF metadata from JPEG, TIF, PNG, GIF, BMP, CR2 and NEF files. More information at https://code.adonline.id.au/reading-exif-data-in-python/
#!/usr/bin/env python
import imageio
import exifread
from PIL import Image, ExifTags
from PIL.ExifTags import TAGS
from PIL.PngImagePlugin import PngImageFile, PngInfo
import re
import os
from rawphoto.cr2 import Cr2
from rawphoto.nef import Nef
import argparse
def options():
parser = argparse.ArgumentParser(description="Read image metadata")
parser.add_argument("-i", "--image", help="Input image file.", required=True)
args = parser.parse_args()
return args
# Via https://gist.github.com/SamWhited/af58edaed66414bded84
def raw_metadata(raw, ifd, level=1):
for name in ifd.entries:
e = ifd.entries[name]
if name in ifd.subifds or isinstance(name, tuple):
if isinstance(name, tuple):
for n in name:
print(level * "\t" + n + ":")
raw_metadata(raw, ifd.subifds[n], level + 1)
else:
print(level * "\t" + name + ":")
raw_metadata(raw, ifd.subifds[name], level + 1)
else:
if isinstance(name, str):
if e.tag_type_key is 0x07:
print(level * "\t" + "{}: {}".format(
name,
"[Binary blob]"
))
else:
print(level * "\t" + "{}: {}".format(
name,
ifd.get_value(e)
))
def gen_metadata(image):
# Read image into imageio for data type
pic = imageio.imread(image)
# Read image into PIL to extract basic metadata
type = Image.open(image)
# Calculations
megapixels = (type.size[0]*type.size[1]/1000000) # Megapixels
d = re.sub(r'[a-z]', '', str(pic.dtype)) # Dtype
t = len(Image.Image.getbands(type)) # Number of channels
print("\n--Summary--\n")
print("Filename: ",type.filename)
print("Format: ", type.format)
print("Data Type:", pic.dtype)
print("Bit Depth (per Channel):", d)
print("Bit Depth (per Pixel): ", int(d)*int(t))
print("Number of Channels: ", t)
print("Mode: ",type.mode)
print("Palette: ",type.palette)
print("Width: ", type.size[0])
print("Height: ", type.size[1])
print("Megapixels: ",megapixels)
# Open image with ExifMode to collect EXIF data
exif_tags = open(image, 'rb')
tags = exifread.process_file(exif_tags)
# Create an empty array
exif_array = []
# Print header
print("\n--Metadata--\n")
# For non-PNGs
if type.format != "PNG":
# Compile array from tags dict
for i in tags:
compile = i, str(tags[i])
exif_array.append(compile)
for properties in exif_array:
if properties[0] != 'JPEGThumbnail':
print(': '.join(str(x) for x in properties))
if type.format == "PNG":
image = PngImageFile(image) #via https://stackoverflow.com/a/58399815
metadata = PngInfo()
# Compile array from tags dict
for i in image.text:
compile = i, str(image.text[i])
exif_array.append(compile)
# If XML metadata, pull out data by idenifying data type and gathering useful meta
if len(exif_array) > 0:
header = exif_array[0][0]
else:
header = ""
print("No available metadata")
xml_output = []
if header.startswith("XML"):
xml = exif_array[0][1]
xml_output.extend(xml.splitlines()) # Use splitlines so that you have a list containing each line
# Remove useless meta tags
for line in xml.splitlines():
if "<" not in line:
if "xmlns" not in line:
# Remove equal signs, quotation marks, /> characters and leading spaces
xml_line = re.sub(r'[a-z]*:', '', line).replace('="', ': ')
xml_line = xml_line.rstrip(' />')
xml_line = xml_line.rstrip('\"')
xml_line = xml_line.lstrip(' ')
print(xml_line)
elif header.startswith("Software"):
print("No available metadata")
# If no XML, print available metadata
else:
for properties in exif_array:
if properties[0] != 'JPEGThumbnail':
print(': '.join(str(x) for x in properties))
# Explanation for GIF or BMP
if type.format == "GIF" or type.format == "BMP":
print("No available metadata")
def main():
# Get options
args = options()
image = args.image
# Check for RAW images
name, extension = os.path.splitext(image)
# List valid extensions
ext = [".png", ".jpg", ".jpeg", ".cr2", ".nef", ".tif", ".bmp"]
if extension not in ext:
print("File format ",extension," not supported.")
exit()
if extension == ".CR2":
metadata = {}
filepath = image
(filepath_no_ext, ext) = os.path.splitext(filepath)
filename_no_ext = os.path.basename(filepath_no_ext)
ext = ext.upper()
if ext == '.CR2':
raw = Cr2(filename=filepath)
elif ext == '.NEF':
raw = Nef(filename=filepath)
else:
raise TypeError("Format not supported")
for i in range(len(raw.ifds)):
ifd = raw.ifds[i]
print("IFD #{}".format(i))
raw_metadata(raw, ifd)
# Hax.
for subifd in ifd.subifds:
if isinstance(subifd, int):
print("Subifd ", subifd)
raw_metadata(raw, ifd.subifds[subifd], 1)
raw.close()
else:
gen_metadata(image)
if __name__ == '__main__':
main()
@alexantao
Copy link

Hi Adams, Thank you.
I've learned a lot with this example. I'm making a python program that wil read my entire library, searching for all ny Favorites images (those with EXIF tag Rating: 5), and mopy (or link) them on a another directory.
But I have a lot of RAW (Nikon NEF) images . I tried this funcions of yours, it lists the Metadata of the files, but cannot find the EXIF data of it in the XMP section that stores the Rating.

If I run exiftool on the image, I get (among others):
TIFF-EP Standard ID : 1 0 0 0 Jpg From Raw : (Binary data 815067 bytes, use -b option to extract) Thumbnail TIFF : (Binary data 57816 bytes, use -b option to extract) ---- XMP ---- XMP Toolkit : XMP Core 4.4.0-Exiv2 Rating : 5 Rating Percent : 99 ---- MakerNotes ---- Maker Note Version : 2.10 Quality : RAW White Balance : Auto Focus Mode : AF-A

But these funcions show only the sections:
exif , raw_data and preview_image

Do you know how can I get the other information ?

Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment