Commit 340b3dda authored by Joel Collins's avatar Joel Collins
Browse files

Initial testing for exif metadata

parent 2314084c
Loading
Loading
Loading
Loading
+0 −11
Original line number Diff line number Diff line
@@ -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

+57 −43
Original line number Diff line number Diff line
@@ -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

    # Populate capture parameters
    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']
    capture.id = capture_dict['metadata']['id']
    capture.timestring = capture_dict['metadata']['time']
    capture.format = capture_dict['metadata']['format']

    if 'custom' in capture_dict['metadata']:
        capture._metadata = capture_dict['metadata']['custom']
    md_exif = pull_usercomment_dict(capture.file)
    md_database = capture_dict['metadata']

    if 'tags' in capture_dict['metadata']:
        capture.tags = capture_dict['metadata']['tags']
    # Populate capture parameters
    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)

    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,7 +184,6 @@ class CaptureObject(object):
            self.stream = self.file

        # Save initial metadata file
        if create_metadata_file:
        self.save_metadata()

    def build_file_path(
@@ -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."""