Initial testing for exif metadata

This commit is contained in:
Joel Collins 2019-02-18 10:53:18 +00:00
parent 2314084cb1
commit 340b3dda89
2 changed files with 59 additions and 56 deletions

View file

@ -248,17 +248,6 @@ class BaseCamera(object):
for capture in capture_list:
if capture.file_exists:
valid_captures.append(capture)
else:
if os.path.isfile(capture.metadata_file):
logging.warning("Capture data missing. Deleting metadata file {}.".format(capture.metadata_file))
os.remove(capture.metadata_file)
# Validate and repair captures
for capture in valid_captures:
# If the captures metadata file is missing
if not os.path.isfile(capture.metadata_file):
# Recreate the metadata file
capture.save_metadata()
return valid_captures

View file

@ -9,6 +9,8 @@ import logging
from PIL import Image
import atexit
import piexif
pil_formats = ['JPG', 'JPEG', 'PNG', 'TIF', 'TIFF']
thumbnail_size = (200, 150)
@ -30,6 +32,30 @@ def clear_tmp():
logging.debug("Cleared {}.".format(TEMP_CAPTURE_PATH))
def pull_usercomment_dict(filepath):
"""
Reads UserComment Exif data from a file, and returns the contained bytes as a dictionary.
Args:
filepath: Path to the Exif-containing file
"""
exif_dict = piexif.load(filepath)
if 'Exif' in exif_dict and 37510 in exif_dict['Exif']:
return yaml.load(exif_dict['Exif'][37510].decode())
else:
return {}
def extract_with_priority(key, best_dict, backup_dict):
if key in best_dict:
return best_dict[key]
elif key in backup_dict:
logging.warning("Key {} not found in primary dictionary. Falling back to backup.".format(key))
return backup_dict[key]
else:
logging.error("Key {} not found in either dictionary!".format(key))
return None
def capture_from_dict(capture_dict):
"""
Creates an instance of CaptureObject from a dictionary of capture information.
@ -39,22 +65,24 @@ def capture_from_dict(capture_dict):
Args:
capture_dict (dict): Dictionary containing capture information
"""
capture = CaptureObject(create_metadata_file=False) # Create a placeholder capture
capture = CaptureObject() # Create a placeholder capture
# Get inherent capture information from database
capture.file = capture_dict['path']
capture.split_file_path(capture.file)
capture.temporary = capture_dict['temporary']
md_exif = pull_usercomment_dict(capture.file)
md_database = capture_dict['metadata']
# Populate capture parameters
capture.temporary = capture_dict['temporary']
capture.id = capture_dict['metadata']['id']
capture.timestring = capture_dict['metadata']['time']
capture.format = capture_dict['metadata']['format']
capture.id = extract_with_priority('id', md_exif, md_database)
capture.timestring = extract_with_priority('time', md_exif, md_database)
capture.format = extract_with_priority('format', md_exif, md_database)
if 'custom' in capture_dict['metadata']:
capture._metadata = capture_dict['metadata']['custom']
if 'tags' in capture_dict['metadata']:
capture.tags = capture_dict['metadata']['tags']
capture.file = capture_dict['metadata']['path']
capture.split_file_path(capture.file)
capture._metadata = extract_with_priority('custom', md_exif, md_database)
capture.tags = extract_with_priority('tags', md_exif, md_database)
return capture
@ -72,8 +100,7 @@ class CaptureObject(object):
temporary: bool = False,
filename: str = '',
folder: str = '',
fmt: str = '',
create_metadata_file: bool = True) -> None:
fmt: str = '') -> None:
"""Create a new StreamObject, to manage capture data."""
# Store a nice ID
@ -107,7 +134,7 @@ class CaptureObject(object):
self.tags = [] #: list: List of tags. Essentially just as extra custom metadata field, but useful for quick organisation
# Initialise the capture stream
self.initialise_capture(create_metadata_file=create_metadata_file)
self.initialise_capture()
# Log if created by context manager
self.context_manager = False
@ -136,14 +163,12 @@ class CaptureObject(object):
logging.info("Cleaning up {}".format(self.id))
self.close()
def initialise_capture(self, create_metadata_file=True):
def initialise_capture(self):
"""
Initialise the capture object. Creates a file name and builds a file path,
creates an in-memory byte stream for capture data, and creates a metadata
file on disk.
Args:
create_metadata_file (bool): Skips creating a metadata file if False (useful for captures being processed but not stored)
"""
self.build_file_path(self.filename, self.folder)
@ -159,8 +184,7 @@ class CaptureObject(object):
self.stream = self.file
# Save initial metadata file
if create_metadata_file:
self.save_metadata()
self.save_metadata()
def build_file_path(
self,
@ -263,13 +287,6 @@ class CaptureObject(object):
# HANDLE METADATA
@property
def metadata_file(self) -> str:
"""
Path to the associated on-disk metadata YAML file.
"""
return "{}.yaml".format(os.path.splitext(self.file)[0])
def put_metadata(self, data: dict) -> None:
"""
Merge metadata from a passed dictionary into the capture metadata, and saves.
@ -282,19 +299,21 @@ class CaptureObject(object):
def save_metadata(self) -> None:
"""
Save metadata to on-disk metadata YAML file
Save metadata to exif
"""
logging.debug("Writing metadata to file {}".format(self.metadata_file))
with open(self.metadata_file, 'w') as outfile:
yaml.dump(self.metadata, outfile, default_flow_style=False)
def delete_metadata(self) -> None:
"""
Delete on-disk metadata YAML file, if it exists
"""
if os.path.isfile(self.metadata_file):
logging.info("Deleting file {}".format(self.metadata_file))
os.remove(self.metadata_file)
if self.file_exists:
logging.debug("Writing exif data to capture file")
# Extract current Exif data
exif_dict = piexif.load(self.file)
# Serialize metadata
metadata_string = yaml.safe_dump(self.metadata)
# Insert metadata into exif_dict
exif_dict['Exif'][piexif.ExifIFD.UserComment] = metadata_string.encode()
# Convert new exif dict to exif bytes
exif_bytes = piexif.dump(exif_dict)
# Insert exif into file
piexif.insert(exif_bytes, self.file)
@property
def metadata(self) -> dict:
@ -331,8 +350,7 @@ class CaptureObject(object):
"""
# Create basic state dictionary
d = {'locked': self.locked, 'temporary': self.temporary, 'metadata': self.metadata,
'metadata_path': self.metadata_file}
d = {'path': self.file, 'locked': self.locked, 'temporary': self.temporary, 'metadata': self.metadata}
# Check bytestream
if self.stream_exists:
@ -445,9 +463,6 @@ class CaptureObject(object):
def delete_file(self) -> bool:
"""If the StreamObject has been saved, delete the file."""
if os.path.isfile(self.metadata_file):
logging.info("Deleting file {}".format(self.metadata_file))
os.remove(self.metadata_file)
if os.path.isfile(self.file):
logging.info("Deleting file {}".format(self.file))
@ -462,7 +477,6 @@ class CaptureObject(object):
logging.info("Deleting {}".format(self.id))
self.delete_stream()
self.delete_file()
self.delete_metadata()
def shunt(self):
"""Demote the StreamObject from being stored in memory."""