Blackened everything

This commit is contained in:
Joel Collins 2019-09-15 14:17:52 +01:00
parent e213647217
commit 5966ce29be
57 changed files with 1938 additions and 1414 deletions

View file

@ -56,6 +56,7 @@ class CameraEvent(object):
An event-like class that signals all active clients when a new frame is available.
"""
def __init__(self):
self.events = {}
@ -112,9 +113,10 @@ class BaseCamera(metaclass=ABCMeta):
images (list): List of image capture objects
videos (list): List of video capture objects
"""
def __init__(self):
self.thread = None
self.camera = None
self.camera = None
self.lock = StrictLock(timeout=1)
@ -128,11 +130,11 @@ class BaseCamera(metaclass=ABCMeta):
self.state = {}
self.paths = {
'image': BASE_CAPTURE_PATH,
'video': BASE_CAPTURE_PATH,
'image_tmp': TEMP_CAPTURE_PATH,
'video_tpm': TEMP_CAPTURE_PATH
}
"image": BASE_CAPTURE_PATH,
"video": BASE_CAPTURE_PATH,
"image_tmp": TEMP_CAPTURE_PATH,
"video_tpm": TEMP_CAPTURE_PATH,
}
# Capture data
self.images = []
@ -176,7 +178,7 @@ class BaseCamera(metaclass=ABCMeta):
self.last_access = time.time()
self.stop = False
if not self.state['stream_active']:
if not self.state["stream_active"]:
# start background frame thread
self.thread = threading.Thread(target=self._thread)
self.thread.daemon = True
@ -196,12 +198,12 @@ class BaseCamera(metaclass=ABCMeta):
logging.debug("Stopping worker thread")
timeout_time = time.time() + timeout
if self.state['stream_active']:
if self.state["stream_active"]:
self.stop = True
self.thread.join() # Wait for stream thread to exit
logging.debug("Waiting for stream thread to exit.")
while self.state['stream_active']:
while self.state["stream_active"]:
if time.time() > timeout_time:
logging.debug("Timeout waiting for worker thread close.")
raise TimeoutError("Timeout waiting for worker thread close.")
@ -249,12 +251,13 @@ class BaseCamera(metaclass=ABCMeta):
# CREATING NEW CAPTURES
def new_image(
self,
write_to_file: bool = True,
temporary: bool = True,
filename: str = None,
folder: str = "",
fmt: str = 'jpeg'):
self,
write_to_file: bool = True,
temporary: bool = True,
filename: str = None,
folder: str = "",
fmt: str = "jpeg",
):
"""
Create a new image capture object. Adds to the image list, and shunt all others.
@ -275,7 +278,7 @@ class BaseCamera(metaclass=ABCMeta):
filename = "{}.{}".format(filename, fmt)
# Generate folder
base_folder = self.paths['image_tmp'] if temporary else self.paths['image']
base_folder = self.paths["image_tmp"] if temporary else self.paths["image"]
folder = os.path.join(base_folder, folder)
# Generate file path
@ -283,9 +286,8 @@ class BaseCamera(metaclass=ABCMeta):
# Create capture object
output = CaptureObject(
write_to_file=write_to_file,
temporary=temporary,
filepath=filepath)
write_to_file=write_to_file, temporary=temporary, filepath=filepath
)
# Update capture list
shunt_captures(self.images)
@ -294,12 +296,13 @@ class BaseCamera(metaclass=ABCMeta):
return output
def new_video(
self,
write_to_file: bool = True,
temporary: bool = False,
filename: str = None,
folder: str = "",
fmt: str = 'h264'):
self,
write_to_file: bool = True,
temporary: bool = False,
filename: str = None,
folder: str = "",
fmt: str = "h264",
):
"""
Create a new video capture object. Adds to the image list, and shunt all others.
@ -320,7 +323,7 @@ class BaseCamera(metaclass=ABCMeta):
filename = "{}.{}".format(filename, fmt)
# Generate folder
base_folder = self.paths['video_tmp'] if temporary else self.paths['video']
base_folder = self.paths["video_tmp"] if temporary else self.paths["video"]
folder = os.path.join(base_folder, folder)
# Generate file path
@ -328,9 +331,8 @@ class BaseCamera(metaclass=ABCMeta):
# Create capture object
output = CaptureObject(
write_to_file=write_to_file,
temporary=temporary,
filepath=filepath)
write_to_file=write_to_file, temporary=temporary, filepath=filepath
)
# Update capture list
shunt_captures(self.videos)
@ -345,7 +347,7 @@ class BaseCamera(metaclass=ABCMeta):
self.frames_iterator = self.frames()
logging.debug("Entering worker thread.")
self.state['stream_active'] = True
self.state["stream_active"] = True
for frame in self.frames_iterator:
self.frame = frame
@ -354,9 +356,13 @@ class BaseCamera(metaclass=ABCMeta):
# Handle timeout
if (
self.stream_timeout_enabled and # If using timeout
(time.time() - self.last_access > self.stream_timeout) and # And timeout time
not self.state['preview_active'] # And GPU preview is not active
self.stream_timeout_enabled
and ( # If using timeout
time.time() - self.last_access > self.stream_timeout
)
and not self.state[ # And timeout time
"preview_active"
] # And GPU preview is not active
):
self.frames_iterator.close()
break
@ -372,4 +378,4 @@ class BaseCamera(metaclass=ABCMeta):
logging.debug("BaseCamera worker thread exiting...")
# Set stream_activate state
self.state['stream_active'] = False
self.state["stream_active"] = False

View file

@ -18,12 +18,12 @@ Attributes:
TEMP_CAPTURE_PATH (str): Base path to store all temporary captures (automatically emptied)
"""
PIL_FORMATS = ['JPG', 'JPEG', 'PNG', 'TIF', 'TIFF']
EXIF_FORMATS = ['JPG', 'JPEG', 'TIF', 'TIFF']
PIL_FORMATS = ["JPG", "JPEG", "PNG", "TIF", "TIFF"]
EXIF_FORMATS = ["JPG", "JPEG", "TIF", "TIFF"]
THUMBNAIL_SIZE = (200, 150)
BASE_CAPTURE_PATH = os.path.join(os.path.expanduser('~'), 'micrographs')
TEMP_CAPTURE_PATH = os.path.join(BASE_CAPTURE_PATH, 'tmp')
BASE_CAPTURE_PATH = os.path.join(os.path.expanduser("~"), "micrographs")
TEMP_CAPTURE_PATH = os.path.join(BASE_CAPTURE_PATH, "tmp")
# TODO: Move these methods to a camera utilities module?
@ -50,8 +50,8 @@ def pull_usercomment_dict(filepath):
except InvalidImageDataError:
logging.error("Invalid data at {}. Skipping.".format(filepath))
return None
if 'Exif' in exif_dict and 37510 in exif_dict['Exif']:
return yaml.load(exif_dict['Exif'][37510].decode())
if "Exif" in exif_dict and 37510 in exif_dict["Exif"]:
return yaml.load(exif_dict["Exif"][37510].decode())
else:
return None
@ -59,7 +59,9 @@ def pull_usercomment_dict(filepath):
def make_file_list(directory, formats):
files = []
for fmt in formats:
files.extend(glob.glob('{}/**/*.{}'.format(directory, fmt.lower()), recursive=True))
files.extend(
glob.glob("{}/**/*.{}".format(directory, fmt.lower()), recursive=True)
)
logging.info("{} capture files found on disk".format(len(files)))
@ -98,21 +100,19 @@ def capture_from_exif(path, exif_dict):
"""
# Create a placeholder capture
capture = CaptureObject(
filepath=path
)
capture = CaptureObject(filepath=path)
# Build file path information
capture.split_file_path(capture.file)
# Populate capture parameters
capture.id = exif_dict['id']
capture.timestring = exif_dict['time']
capture.format = exif_dict['format']
capture.id = exif_dict["id"]
capture._metadata = exif_dict['custom']
capture.tags = exif_dict['tags']
capture.timestring = exif_dict["time"]
capture.format = exif_dict["format"]
capture._metadata = exif_dict["custom"]
capture.tags = exif_dict["tags"]
return capture
@ -138,10 +138,8 @@ class CaptureObject(object):
"""
def __init__(
self,
write_to_file: bool = False,
temporary: bool = False,
filepath: str = '') -> None:
self, write_to_file: bool = False, temporary: bool = False, filepath: str = ""
) -> None:
"""Create a new StreamObject, to manage capture data."""
# Store a nice ID
@ -184,7 +182,9 @@ class CaptureObject(object):
"""Create StreamObject in context, to auto-clean disk data."""
logging.debug(
"Entering context for {}. Stored files will be cleaned up automatically regardless of location.".format(
self.id))
self.id
)
)
self.temporary = True # Flag file to be removed on close.
self.context_manager = True # Used in metadata
@ -203,12 +203,14 @@ class CaptureObject(object):
Construct a full file path, based on filename, folder, and file format.
Defaults to UUID.
"""
global TEMP_CAPTURE_PATH, BASE_CAPTURE_PATH
global TEMP_CAPTURE_PATH, BASE_CAPTURE_PATH
if self.temporary:
base_dir = TEMP_CAPTURE_PATH
else:
base_dir = BASE_CAPTURE_PATH
return os.path.join(base_dir, self.filename) # Full file name by joining given folder to given name
return os.path.join(
base_dir, self.filename
) # Full file name by joining given folder to given name
def split_file_path(self, filepath):
"""
@ -221,7 +223,7 @@ class CaptureObject(object):
self.filefolder, self.filename = os.path.split(filepath)
# Split the filename out from it's file extension
self.basename = os.path.splitext(self.filename)[0]
self.format = self.filename.split('.')[-1]
self.format = self.filename.split(".")[-1]
# Create folder and file
if not os.path.exists(self.filefolder):
@ -298,7 +300,7 @@ class CaptureObject(object):
# Serialize metadata
metadata_string = yaml.safe_dump(self.metadata)
# Insert metadata into exif_dict
exif_dict['Exif'][piexif.ExifIFD.UserComment] = metadata_string.encode()
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
@ -310,8 +312,14 @@ class CaptureObject(object):
Create basic metadata dictionary from basic capture data,
and any added custom metadata and tags.
"""
d = {'id': self.id, 'filename': self.filename, 'time': self.timestring,
'format': self.format, 'tags': self.tags, 'custom': self._metadata}
d = {
"id": self.id,
"filename": self.filename,
"time": self.timestring,
"format": self.format,
"tags": self.tags,
"custom": self._metadata,
}
# Add custom metadata to dictionary
return d
@ -339,19 +347,19 @@ class CaptureObject(object):
"""
# Create basic state dictionary
d = {'path': self.file, 'temporary': self.temporary, 'metadata': self.metadata}
d = {"path": self.file, "temporary": self.temporary, "metadata": self.metadata}
# Check bytestream
if self.stream_exists:
d['bytestream'] = True
d["bytestream"] = True
else:
d['bytestream'] = False
d["bytestream"] = False
# Combined availability of data
if self.exists:
d['available'] = True
d["available"] = True
else:
d['available'] = False
d["available"] = False
return d
@ -373,7 +381,7 @@ class CaptureObject(object):
else: # If data bytestream is empty
if self.file_exists: # If data file exists
logging.info("Opening from file {}".format(self.file))
with open(self.file, 'rb') as f:
with open(self.file, "rb") as f:
d = io.BytesIO(f.read()) # Load bytes from file
d.seek(0) # Rewind loaded bytestream
# Create a copy of the bytestream bytes
@ -413,7 +421,7 @@ class CaptureObject(object):
def load_file(self) -> bool:
"""Load data stored on disk to the in-memory bytestream."""
if self.file_exists: # If data file exists
with open(self.file, 'rb') as f:
with open(self.file, "rb") as f:
self.bytestream = io.BytesIO(f.read()) # Load bytes from file
self.bytestream.seek(0) # Rewind data bytes again
return True
@ -423,7 +431,7 @@ class CaptureObject(object):
def save_file(self) -> bool:
"""Write the StreamObjects bytestream to a file."""
if self.stream_exists: # If there's a bytestream to save
with open(self.file, 'ab') as f: # Load file as bytes
with open(self.file, "ab") as f: # Load file as bytes
logging.debug("Writing bytestream to file {}".format(self.file))
f.seek(0, 0) # Seek to the start of the file
f.write(self.binary) # Write data bytes to file

View file

@ -36,6 +36,7 @@ import picamera.array
from typing import Tuple
from .base import BaseCamera, CaptureObject
# Richard's fix gain
from .set_picamera_gain import set_analog_gain, set_digital_gain
@ -43,30 +44,31 @@ from .set_picamera_gain import set_analog_gain, set_digital_gain
# MAIN CLASS
class PiCameraStreamer(BaseCamera):
"""Raspberry Pi camera implementation of PiCameraStreamer."""
picamera_settings_keys = [
'exposure_mode',
'analog_gain',
'digital_gain',
'shutter_speed',
'awb_gains',
'awb_mode',
'framerate',
'saturation',
'lens_shading_table'
"exposure_mode",
"analog_gain",
"digital_gain",
"shutter_speed",
"awb_gains",
"awb_mode",
"framerate",
"saturation",
"lens_shading_table",
]
def __init__(self):
# Run BaseCamera init
BaseCamera.__init__(self)
# Attach to Pi camera
self.camera = picamera.PiCamera() #: :py:class:`picamera.PiCamera`: Picamera object
self.camera = (
picamera.PiCamera()
) #: :py:class:`picamera.PiCamera`: Picamera object
# Store state of PiCameraStreamer
self.state.update({
'stream_active': False,
'record_active': False,
'preview_active': False,
})
self.state.update(
{"stream_active": False, "record_active": False, "preview_active": False}
)
# Reset variable states
self.set_zoom(1.0)
@ -101,11 +103,11 @@ class PiCameraStreamer(BaseCamera):
"""
conf_dict = {
'stream_resolution': self.stream_resolution,
'image_resolution': self.image_resolution,
'numpy_resolution': self.numpy_resolution,
'jpeg_quality': self.jpeg_quality,
'picamera_settings': {},
"stream_resolution": self.stream_resolution,
"image_resolution": self.image_resolution,
"numpy_resolution": self.numpy_resolution,
"jpeg_quality": self.jpeg_quality,
"picamera_settings": {},
}
# PiCamera parameters
@ -113,7 +115,7 @@ class PiCameraStreamer(BaseCamera):
try:
value = getattr(self.camera, key)
logging.debug("Reading PiCamera().{}: {}".format(key, value))
conf_dict['picamera_settings'][key] = value
conf_dict["picamera_settings"][key] = value
except AttributeError:
logging.debug("Unable to read PiCamera attribute {}".format(key))
@ -138,21 +140,23 @@ class PiCameraStreamer(BaseCamera):
with self.lock:
# Apply valid config params to Picamera object
if not self.state['record_active']: # If not recording a video
if not self.state["record_active"]: # If not recording a video
# Pause stream while changing settings
if self.state['stream_active']: # If stream is active
if self.state["stream_active"]: # If stream is active
logging.info("Pausing stream to update config.")
self.stop_stream_recording() # Pause stream
paused_stream = True # Remember to unpause stream when done
# PiCamera parameters
if 'picamera_settings' in config: # If new settings are given
self.apply_picamera_settings(config['picamera_settings'], pause_for_effect=True)
if "picamera_settings" in config: # If new settings are given
self.apply_picamera_settings(
config["picamera_settings"], pause_for_effect=True
)
# PiCameraStreamer parameters
for key, value in config.items(): # For each provided setting
if (key != 'picamera_settings') and hasattr(self, key):
if (key != "picamera_settings") and hasattr(self, key):
setattr(self, key, value)
# If stream was paused to update config, unpause
@ -162,67 +166,84 @@ class PiCameraStreamer(BaseCamera):
else:
raise Exception(
"Cannot update camera config while recording is active.")
"Cannot update camera config while recording is active."
)
def apply_picamera_settings(self, settings_dict: dict, pause_for_effect: bool=True):
def apply_picamera_settings(
self, settings_dict: dict, pause_for_effect: bool = True
):
# Set exposure mode
if 'exposure_mode' in settings_dict:
logging.debug("Applying exposure_mode: {}".format(settings_dict['exposure_mode']))
self.camera.exposure_mode = settings_dict['exposure_mode']
if "exposure_mode" in settings_dict:
logging.debug(
"Applying exposure_mode: {}".format(settings_dict["exposure_mode"])
)
self.camera.exposure_mode = settings_dict["exposure_mode"]
# Apply gains and let them settle
if 'analog_gain' in settings_dict:
logging.debug("Applying analog_gain: {}".format(settings_dict['analog_gain']))
set_analog_gain(self.camera, float(settings_dict['analog_gain']))
if 'digital_gain' in settings_dict:
logging.debug("Applying digital_gain: {}".format(settings_dict['digital_gain']))
set_digital_gain(self.camera, float(settings_dict['digital_gain']))
if "analog_gain" in settings_dict:
logging.debug(
"Applying analog_gain: {}".format(settings_dict["analog_gain"])
)
set_analog_gain(self.camera, float(settings_dict["analog_gain"]))
if "digital_gain" in settings_dict:
logging.debug(
"Applying digital_gain: {}".format(settings_dict["digital_gain"])
)
set_digital_gain(self.camera, float(settings_dict["digital_gain"]))
# Apply shutter speed
if 'shutter_speed' in settings_dict:
logging.debug("Applying shutter_speed: {}".format(settings_dict['shutter_speed']))
self.camera.shutter_speed = int(settings_dict['shutter_speed'])
if "shutter_speed" in settings_dict:
logging.debug(
"Applying shutter_speed: {}".format(settings_dict["shutter_speed"])
)
self.camera.shutter_speed = int(settings_dict["shutter_speed"])
time.sleep(0.2) # Let gains settle
# Handle AWB in a half-smart way
if 'awb_gains' in settings_dict:
if "awb_gains" in settings_dict:
logging.debug("Applying awb_mode: off")
self.camera.awb_mode = 'off'
logging.debug("Applying awb_gains: {}".format(settings_dict['awb_gains']))
self.camera.awb_gains = settings_dict['awb_gains']
elif 'awb_mode' in settings_dict:
logging.debug("Applying awb_mode: {}".format(settings_dict['awb_mode']))
self.camera.awb_mode = settings_dict['awb_mode']
self.camera.awb_mode = "off"
logging.debug("Applying awb_gains: {}".format(settings_dict["awb_gains"]))
self.camera.awb_gains = settings_dict["awb_gains"]
elif "awb_mode" in settings_dict:
logging.debug("Applying awb_mode: {}".format(settings_dict["awb_mode"]))
self.camera.awb_mode = settings_dict["awb_mode"]
# Handle some properties that can be quickly applied
batched_keys = ['framerate', 'saturation']
batched_keys = ["framerate", "saturation"]
for key in batched_keys:
if (key in settings_dict) and hasattr(self.camera, key):
logging.debug("Applying {}: {}".format(key, settings_dict[key]))
setattr(self.camera, key, settings_dict[key])
# Handle lens shading if camera supports it
if ('lens_shading_table' in settings_dict) and hasattr(self.camera, 'lens_shading_table'):
logging.debug("Applying lens_shading_table: {}".format(settings_dict['lens_shading_table']))
self.camera.lens_shading_table = settings_dict['lens_shading_table']
if ("lens_shading_table" in settings_dict) and hasattr(
self.camera, "lens_shading_table"
):
logging.debug(
"Applying lens_shading_table: {}".format(
settings_dict["lens_shading_table"]
)
)
self.camera.lens_shading_table = settings_dict["lens_shading_table"]
# Final optional pause to settle
if pause_for_effect:
time.sleep(0.2)
def set_zoom(self, zoom_value: float = 1.) -> None:
def set_zoom(self, zoom_value: float = 1.0) -> None:
"""
Change the camera zoom, handling re-centering and scaling.
"""
with self.lock:
self.state['zoom_value'] = float(zoom_value)
if self.state['zoom_value'] < 1:
self.state['zoom_value'] = 1
self.state["zoom_value"] = float(zoom_value)
if self.state["zoom_value"] < 1:
self.state["zoom_value"] = 1
# Richard's code for zooming !
fov = self.camera.zoom
centre = np.array([fov[0] + fov[2] / 2.0, fov[1] + fov[3] / 2.0])
size = 1.0 / self.state['zoom_value']
size = 1.0 / self.state["zoom_value"]
# If the new zoom value would be invalid, move the centre to
# keep it within the camera's sensor (this is only relevant
# when zooming out, if the FoV is not centred on (0.5, 0.5)
@ -252,22 +273,24 @@ class PiCameraStreamer(BaseCamera):
self.camera.preview.window = window
if fullscreen:
self.camera.preview.fullscreen = fullscreen
self.state['preview_active'] = True
self.state["preview_active"] = True
except picamera.exc.PiCameraMMALError as e:
logging.error("Suppressed a MMALError in start_preview. Exception: {}".format(e))
logging.error(
"Suppressed a MMALError in start_preview. Exception: {}".format(e)
)
except picamera.exc.PiCameraValueError as e:
logging.error("Suppressed a ValueError exception in start_preview. Exception: {}".format(e))
logging.error(
"Suppressed a ValueError exception in start_preview. Exception: {}".format(
e
)
)
def stop_preview(self):
"""Stop the on board GPU camera preview."""
self.camera.stop_preview()
self.state['preview_active'] = False
self.state["preview_active"] = False
def start_recording(
self,
output,
fmt: str = 'h264',
quality: int = 15):
def start_recording(self, output, fmt: str = "h264", quality: int = 15):
"""Start recording.
Start a new video recording, writing to a output object.
@ -283,7 +306,7 @@ class PiCameraStreamer(BaseCamera):
"""
with self.lock:
# Start recording method only if a current recording is not running
if not self.state['record_active']:
if not self.state["record_active"]:
# If output is a StreamObject
if isinstance(output, CaptureObject):
@ -300,17 +323,19 @@ class PiCameraStreamer(BaseCamera):
format=fmt,
splitter_port=2,
resize=self.stream_resolution,
quality=quality)
quality=quality,
)
# Update state dictionary
self.state['record_active'] = True
self.state["record_active"] = True
return output
else:
logging.error(
"Cannot start a new recording\
until the current recording has stopped.")
until the current recording has stopped."
)
return None
def stop_recording(self):
@ -322,12 +347,11 @@ class PiCameraStreamer(BaseCamera):
logging.info("Recording stopped")
# Update state dictionary
self.state['record_active'] = False
self.state["record_active"] = False
def stop_stream_recording(
self,
splitter_port: int = 1,
resolution: Tuple[int, int] = None) -> None:
self, splitter_port: int = 1, resolution: Tuple[int, int] = None
) -> None:
"""
Sets the camera resolution to the still-image resolution, and stops recording if the stream is active.
@ -346,16 +370,21 @@ class PiCameraStreamer(BaseCamera):
except picamera.exc.PiCameraNotRecording:
logging.info("Not recording on splitter_port {}".format(splitter_port))
else:
logging.info("Stopped MJPEG stream on port {1}. Switching to {0}.".format(resolution, splitter_port))
logging.info(
"Stopped MJPEG stream on port {1}. Switching to {0}.".format(
resolution, splitter_port
)
)
# Increase the resolution for taking an image
time.sleep(0.2) # Sprinkled a sleep to prevent camera getting confused by rapid commands
time.sleep(
0.2
) # Sprinkled a sleep to prevent camera getting confused by rapid commands
self.camera.resolution = resolution
def start_stream_recording(
self,
splitter_port: int = 1,
resolution: Tuple[int, int] = None) -> None:
self, splitter_port: int = 1, resolution: Tuple[int, int] = None
) -> None:
"""
Sets the camera resolution to the video/stream resolution, and starts recording if the stream should be active.
@ -366,45 +395,57 @@ class PiCameraStreamer(BaseCamera):
"""
with self.lock:
# If stream object was destroyed
if not hasattr(self, 'stream'):
if not hasattr(self, "stream"):
self.stream = io.BytesIO() # Create a stream object
# If no explicit resolution is passed
if not resolution:
resolution = self.stream_resolution # Default to video recording resolution
resolution = (
self.stream_resolution
) # Default to video recording resolution
# Reduce the resolution for video streaming
try:
self.camera._check_recording_stopped()
except picamera.exc.PiCameraRuntimeError:
logging.info("Error while changing resolution: Recording already running.")
logging.info(
"Error while changing resolution: Recording already running."
)
else:
self.camera.resolution = resolution
# If the stream should be active
if self.state['stream_active']:
if self.state["stream_active"]:
try:
# Start recording on stream port
self.camera.start_recording(
self.stream,
format='mjpeg',
format="mjpeg",
quality=self.jpeg_quality,
bitrate=-1, # RWB: disable bitrate control
bitrate=-1, # RWB: disable bitrate control
# (bitrate control makes JPEG size less good as a focus
# metric)
splitter_port=splitter_port)
splitter_port=splitter_port,
)
except picamera.exc.PiCameraAlreadyRecording:
logging.info("Error while starting preview: Recording already running.")
logging.info(
"Error while starting preview: Recording already running."
)
else:
logging.debug("Started MJPEG stream at {} on port {}".format(resolution, splitter_port))
logging.debug(
"Started MJPEG stream at {} on port {}".format(
resolution, splitter_port
)
)
def capture(
self,
output,
fmt: str = 'jpeg',
use_video_port: bool = False,
resize: Tuple[int, int] = None,
bayer: bool = True):
self,
output,
fmt: str = "jpeg",
use_video_port: bool = False,
resize: Tuple[int, int] = None,
bayer: bool = True,
):
"""
Capture a still image to a StreamObject.
@ -439,7 +480,8 @@ class PiCameraStreamer(BaseCamera):
quality=100,
resize=resize,
bayer=(not use_video_port) and bayer,
use_video_port=use_video_port)
use_video_port=use_video_port,
)
# Set resolution and start stream recording if necessary
if not use_video_port:
@ -448,9 +490,8 @@ class PiCameraStreamer(BaseCamera):
return output
def yuv(
self,
use_video_port: bool = True,
resize: Tuple[int, int] = None) -> np.ndarray:
self, use_video_port: bool = True, resize: Tuple[int, int] = None
) -> np.ndarray:
"""Capture an uncompressed still YUV image to a Numpy array.
Args:
@ -477,10 +518,8 @@ class PiCameraStreamer(BaseCamera):
logging.info("Capturing to {}".format(output))
self.camera.capture(
output,
resize=size,
format='yuv',
use_video_port=use_video_port)
output, resize=size, format="yuv", use_video_port=use_video_port
)
if not use_video_port:
self.start_stream_recording()
@ -488,9 +527,8 @@ class PiCameraStreamer(BaseCamera):
return output.array
def array(
self,
use_video_port: bool = True,
resize: Tuple[int, int] = None) -> np.ndarray:
self, use_video_port: bool = True, resize: Tuple[int, int] = None
) -> np.ndarray:
"""Capture an uncompressed still RGB image to a Numpy array.
Args:
@ -517,10 +555,8 @@ class PiCameraStreamer(BaseCamera):
logging.info("Capturing to {}".format(output))
self.camera.capture(
output,
resize=size,
format='rgb',
use_video_port=use_video_port)
output, resize=size, format="rgb", use_video_port=use_video_port
)
# Resume stream
self.start_stream_recording()

View file

@ -7,5 +7,4 @@ from ._exif import *
from ._exceptions import *
VERSION = '1.1.2'
VERSION = "1.1.2"

View file

@ -12,20 +12,21 @@ def split_into_segments(data):
head = 2
segments = [b"\xff\xd8"]
while 1:
if data[head: head + 2] == b"\xff\xda":
if data[head : head + 2] == b"\xff\xda":
segments.append(data[head:])
break
else:
length = struct.unpack(">H", data[head + 2: head + 4])[0]
length = struct.unpack(">H", data[head + 2 : head + 4])[0]
endPoint = head + length + 2
seg = data[head: endPoint]
seg = data[head:endPoint]
segments.append(seg)
head = endPoint
if (head >= len(data)):
if head >= len(data):
raise InvalidImageDataError("Wrong JPEG data.")
return segments
def read_exif_from_file(filename):
"""Slices JPEG meta data into a list from JPEG binary data.
"""
@ -39,11 +40,11 @@ def read_exif_from_file(filename):
HEAD_LENGTH = 4
exif = None
while 1:
length = struct.unpack(">H", head[2: 4])[0]
length = struct.unpack(">H", head[2:4])[0]
if head[:2] == b"\xff\xe1":
segment_data = f.read(length - 2)
if segment_data[:4] != b'Exif':
if segment_data[:4] != b"Exif":
head = f.read(HEAD_LENGTH)
continue
exif = head + segment_data
@ -57,6 +58,7 @@ def read_exif_from_file(filename):
f.close()
return exif
def get_exif_seg(segments):
"""Returns Exif from JPEG meta data list
"""
@ -69,9 +71,11 @@ def get_exif_seg(segments):
def merge_segments(segments, exif=b""):
"""Merges Exif with APP0 and APP1 manipulations.
"""
if segments[1][0:2] == b"\xff\xe0" and \
segments[2][0:2] == b"\xff\xe1" and \
segments[2][4:10] == b"Exif\x00\x00":
if (
segments[1][0:2] == b"\xff\xe0"
and segments[2][0:2] == b"\xff\xe1"
and segments[2][4:10] == b"Exif\x00\x00"
):
if exif:
segments[2] = exif
segments.pop(1)
@ -82,8 +86,7 @@ def merge_segments(segments, exif=b""):
elif segments[1][0:2] == b"\xff\xe0":
if exif:
segments[1] = exif
elif segments[1][0:2] == b"\xff\xe1" and \
segments[1][4:10] == b"Exif\x00\x00":
elif segments[1][0:2] == b"\xff\xe1" and segments[1][4:10] == b"Exif\x00\x00":
if exif:
segments[1] = exif
elif exif is None:

View file

@ -31,16 +31,20 @@ def dump(exif_dict_original):
else:
zeroth_ifd = {}
if (("Exif" in exif_dict) and len(exif_dict["Exif"]) or
("Interop" in exif_dict) and len(exif_dict["Interop"]) ):
if (
("Exif" in exif_dict)
and len(exif_dict["Exif"])
or ("Interop" in exif_dict)
and len(exif_dict["Interop"])
):
zeroth_ifd[ImageIFD.ExifTag] = 1
exif_is = True
exif_ifd = exif_dict["Exif"]
if ("Interop" in exif_dict) and len(exif_dict["Interop"]):
exif_ifd[ExifIFD. InteroperabilityTag] = 1
exif_ifd[ExifIFD.InteroperabilityTag] = 1
interop_is = True
interop_ifd = exif_dict["Interop"]
elif ExifIFD. InteroperabilityTag in exif_ifd:
elif ExifIFD.InteroperabilityTag in exif_ifd:
exif_ifd.pop(ExifIFD.InteroperabilityTag)
elif ImageIFD.ExifTag in zeroth_ifd:
zeroth_ifd.pop(ImageIFD.ExifTag)
@ -52,17 +56,20 @@ def dump(exif_dict_original):
elif ImageIFD.GPSTag in zeroth_ifd:
zeroth_ifd.pop(ImageIFD.GPSTag)
if (("1st" in exif_dict) and
("thumbnail" in exif_dict) and
(exif_dict["thumbnail"] is not None)):
if (
("1st" in exif_dict)
and ("thumbnail" in exif_dict)
and (exif_dict["thumbnail"] is not None)
):
first_is = True
exif_dict["1st"][ImageIFD.JPEGInterchangeFormat] = 1
exif_dict["1st"][ImageIFD.JPEGInterchangeFormatLength] = 1
first_ifd = exif_dict["1st"]
zeroth_set = _dict_to_bytes(zeroth_ifd, "0th", 0)
zeroth_length = (len(zeroth_set[0]) + exif_is * 12 + gps_is * 12 + 4 +
len(zeroth_set[1]))
zeroth_length = (
len(zeroth_set[0]) + exif_is * 12 + gps_is * 12 + 4 + len(zeroth_set[1])
)
if exif_is:
exif_set = _dict_to_bytes(exif_ifd, "Exif", zeroth_length)
@ -115,8 +122,7 @@ def dump(exif_dict_original):
else:
gps_pointer = b""
if interop_is:
pointer_value = (TIFF_HEADER_LENGTH +
zeroth_length + exif_length + gps_length)
pointer_value = TIFF_HEADER_LENGTH + zeroth_length + exif_length + gps_length
pointer_str = struct.pack(">I", pointer_value)
key = ExifIFD.InteroperabilityTag
key_str = struct.pack(">H", key)
@ -126,33 +132,46 @@ def dump(exif_dict_original):
else:
interop_pointer = b""
if first_is:
pointer_value = (TIFF_HEADER_LENGTH + zeroth_length +
exif_length + gps_length + interop_length)
pointer_value = (
TIFF_HEADER_LENGTH
+ zeroth_length
+ exif_length
+ gps_length
+ interop_length
)
first_ifd_pointer = struct.pack(">L", pointer_value)
thumbnail_pointer = (pointer_value + len(first_set[0]) + 24 +
4 + len(first_set[1]))
thumbnail_p_bytes = (b"\x02\x01\x00\x04\x00\x00\x00\x01" +
struct.pack(">L", thumbnail_pointer))
thumbnail_length_bytes = (b"\x02\x02\x00\x04\x00\x00\x00\x01" +
struct.pack(">L", len(thumbnail)))
first_bytes = (first_set[0] + thumbnail_p_bytes +
thumbnail_length_bytes + b"\x00\x00\x00\x00" +
first_set[1] + thumbnail)
thumbnail_pointer = (
pointer_value + len(first_set[0]) + 24 + 4 + len(first_set[1])
)
thumbnail_p_bytes = b"\x02\x01\x00\x04\x00\x00\x00\x01" + struct.pack(
">L", thumbnail_pointer
)
thumbnail_length_bytes = b"\x02\x02\x00\x04\x00\x00\x00\x01" + struct.pack(
">L", len(thumbnail)
)
first_bytes = (
first_set[0]
+ thumbnail_p_bytes
+ thumbnail_length_bytes
+ b"\x00\x00\x00\x00"
+ first_set[1]
+ thumbnail
)
else:
first_ifd_pointer = b"\x00\x00\x00\x00"
zeroth_bytes = (zeroth_set[0] + exif_pointer + gps_pointer +
first_ifd_pointer + zeroth_set[1])
zeroth_bytes = (
zeroth_set[0] + exif_pointer + gps_pointer + first_ifd_pointer + zeroth_set[1]
)
if exif_is:
exif_bytes = exif_set[0] + interop_pointer + exif_set[1]
return (header + zeroth_bytes + exif_bytes + gps_bytes +
interop_bytes + first_bytes)
return header + zeroth_bytes + exif_bytes + gps_bytes + interop_bytes + first_bytes
def _get_thumbnail(jpeg):
segments = split_into_segments(jpeg)
while (b"\xff\xe0" <= segments[1][0:2] <= b"\xff\xef"):
while b"\xff\xe0" <= segments[1][0:2] <= b"\xff\xef":
segments.pop(1)
thumbnail = b"".join(segments)
return thumbnail
@ -161,24 +180,31 @@ def _get_thumbnail(jpeg):
def _pack_byte(*args):
return struct.pack("B" * len(args), *args)
def _pack_signed_byte(*args):
return struct.pack("b" * len(args), *args)
def _pack_short(*args):
return struct.pack(">" + "H" * len(args), *args)
def _pack_signed_short(*args):
return struct.pack(">" + "h" * len(args), *args)
def _pack_long(*args):
return struct.pack(">" + "L" * len(args), *args)
def _pack_slong(*args):
return struct.pack(">" + "l" * len(args), *args)
def _pack_float(*args):
return struct.pack(">" + "f" * len(args), *args)
def _pack_double(*args):
return struct.pack(">" + "d" * len(args), *args)
@ -190,16 +216,14 @@ def _value_to_bytes(raw_value, value_type, offset):
if value_type == TYPES.Byte:
length = len(raw_value)
if length <= 4:
value_str = (_pack_byte(*raw_value) +
b"\x00" * (4 - length))
value_str = _pack_byte(*raw_value) + b"\x00" * (4 - length)
else:
value_str = struct.pack(">I", offset)
four_bytes_over = _pack_byte(*raw_value)
elif value_type == TYPES.Short:
length = len(raw_value)
if length <= 2:
value_str = (_pack_short(*raw_value) +
b"\x00\x00" * (2 - length))
value_str = _pack_short(*raw_value) + b"\x00\x00" * (2 - length)
else:
value_str = struct.pack(">I", offset)
four_bytes_over = _pack_short(*raw_value)
@ -241,8 +265,7 @@ def _value_to_bytes(raw_value, value_type, offset):
new_value = b""
for n, val in enumerate(raw_value):
num, den = val
new_value += (struct.pack(">L", num) +
struct.pack(">L", den))
new_value += struct.pack(">L", num) + struct.pack(">L", den)
value_str = struct.pack(">I", offset)
four_bytes_over = new_value
elif value_type == TYPES.SRational:
@ -255,8 +278,7 @@ def _value_to_bytes(raw_value, value_type, offset):
new_value = b""
for n, val in enumerate(raw_value):
num, den = val
new_value += (struct.pack(">l", num) +
struct.pack(">l", den))
new_value += struct.pack(">l", num) + struct.pack(">l", den)
value_str = struct.pack(">I", offset)
four_bytes_over = new_value
elif value_type == TYPES.Undefined:
@ -272,19 +294,17 @@ def _value_to_bytes(raw_value, value_type, offset):
value_str = raw_value + b"\x00" * (4 - length)
except TypeError:
raise ValueError("Got invalid type to convert.")
elif value_type == TYPES.SByte: # Signed Byte
elif value_type == TYPES.SByte: # Signed Byte
length = len(raw_value)
if length <= 4:
value_str = (_pack_signed_byte(*raw_value) +
b"\x00" * (4 - length))
value_str = _pack_signed_byte(*raw_value) + b"\x00" * (4 - length)
else:
value_str = struct.pack(">I", offset)
four_bytes_over = _pack_signed_byte(*raw_value)
elif value_type == TYPES.SShort: # Signed Short
elif value_type == TYPES.SShort: # Signed Short
length = len(raw_value)
if length <= 2:
value_str = (_pack_signed_short(*raw_value) +
b"\x00\x00" * (2 - length))
value_str = _pack_signed_short(*raw_value) + b"\x00\x00" * (2 - length)
else:
value_str = struct.pack(">I", offset)
four_bytes_over = _pack_signed_short(*raw_value)
@ -295,7 +315,7 @@ def _value_to_bytes(raw_value, value_type, offset):
else:
value_str = struct.pack(">I", offset)
four_bytes_over = _pack_float(*raw_value)
elif value_type == TYPES.DFloat: # Double
elif value_type == TYPES.DFloat: # Double
length = len(raw_value)
value_str = struct.pack(">I", offset)
four_bytes_over = _pack_double(*raw_value)
@ -303,6 +323,7 @@ def _value_to_bytes(raw_value, value_type, offset):
length_str = struct.pack(">I", length)
return length_str, value_str, four_bytes_over
def _dict_to_bytes(ifd_dict, ifd, ifd_offset):
tag_count = len(ifd_dict)
entry_header = struct.pack(">H", tag_count)
@ -318,7 +339,10 @@ def _dict_to_bytes(ifd_dict, ifd, ifd_offset):
continue
elif (ifd == "Exif") and (key == ExifIFD.InteroperabilityTag):
continue
elif (ifd == "1st") and (key in (ImageIFD.JPEGInterchangeFormat, ImageIFD.JPEGInterchangeFormatLength)):
elif (ifd == "1st") and (
key
in (ImageIFD.JPEGInterchangeFormat, ImageIFD.JPEGInterchangeFormatLength)
):
continue
raw_value = ifd_dict[key]
@ -332,13 +356,13 @@ def _dict_to_bytes(ifd_dict, ifd, ifd_offset):
offset = TIFF_HEADER_LENGTH + entries_length + ifd_offset + len(values)
try:
length_str, value_str, four_bytes_over = _value_to_bytes(raw_value,
value_type,
offset)
length_str, value_str, four_bytes_over = _value_to_bytes(
raw_value, value_type, offset
)
except ValueError:
raise ValueError(
'"dump" got wrong type of exif value.\n' +
'{0} in {1} IFD. Got as {2}.'.format(key, ifd, type(ifd_dict[key]))
'"dump" got wrong type of exif value.\n'
+ "{0} in {1} IFD. Got as {2}.".format(key, ifd, type(ifd_dict[key]))
)
entries += key_str + type_str + length_str + value_str

View file

@ -14,314 +14,322 @@ class TYPES:
TAGS = {
'Image': {11: {'name': 'ProcessingSoftware', 'type': TYPES.Ascii},
254: {'name': 'NewSubfileType', 'type': TYPES.Long},
255: {'name': 'SubfileType', 'type': TYPES.Short},
256: {'name': 'ImageWidth', 'type': TYPES.Long},
257: {'name': 'ImageLength', 'type': TYPES.Long},
258: {'name': 'BitsPerSample', 'type': TYPES.Short},
259: {'name': 'Compression', 'type': TYPES.Short},
262: {'name': 'PhotometricInterpretation', 'type': TYPES.Short},
263: {'name': 'Threshholding', 'type': TYPES.Short},
264: {'name': 'CellWidth', 'type': TYPES.Short},
265: {'name': 'CellLength', 'type': TYPES.Short},
266: {'name': 'FillOrder', 'type': TYPES.Short},
269: {'name': 'DocumentName', 'type': TYPES.Ascii},
270: {'name': 'ImageDescription', 'type': TYPES.Ascii},
271: {'name': 'Make', 'type': TYPES.Ascii},
272: {'name': 'Model', 'type': TYPES.Ascii},
273: {'name': 'StripOffsets', 'type': TYPES.Long},
274: {'name': 'Orientation', 'type': TYPES.Short},
277: {'name': 'SamplesPerPixel', 'type': TYPES.Short},
278: {'name': 'RowsPerStrip', 'type': TYPES.Long},
279: {'name': 'StripByteCounts', 'type': TYPES.Long},
282: {'name': 'XResolution', 'type': TYPES.Rational},
283: {'name': 'YResolution', 'type': TYPES.Rational},
284: {'name': 'PlanarConfiguration', 'type': TYPES.Short},
290: {'name': 'GrayResponseUnit', 'type': TYPES.Short},
291: {'name': 'GrayResponseCurve', 'type': TYPES.Short},
292: {'name': 'T4Options', 'type': TYPES.Long},
293: {'name': 'T6Options', 'type': TYPES.Long},
296: {'name': 'ResolutionUnit', 'type': TYPES.Short},
301: {'name': 'TransferFunction', 'type': TYPES.Short},
305: {'name': 'Software', 'type': TYPES.Ascii},
306: {'name': 'DateTime', 'type': TYPES.Ascii},
315: {'name': 'Artist', 'type': TYPES.Ascii},
316: {'name': 'HostComputer', 'type': TYPES.Ascii},
317: {'name': 'Predictor', 'type': TYPES.Short},
318: {'name': 'WhitePoint', 'type': TYPES.Rational},
319: {'name': 'PrimaryChromaticities', 'type': TYPES.Rational},
320: {'name': 'ColorMap', 'type': TYPES.Short},
321: {'name': 'HalftoneHints', 'type': TYPES.Short},
322: {'name': 'TileWidth', 'type': TYPES.Short},
323: {'name': 'TileLength', 'type': TYPES.Short},
324: {'name': 'TileOffsets', 'type': TYPES.Short},
325: {'name': 'TileByteCounts', 'type': TYPES.Short},
330: {'name': 'SubIFDs', 'type': TYPES.Long},
332: {'name': 'InkSet', 'type': TYPES.Short},
333: {'name': 'InkNames', 'type': TYPES.Ascii},
334: {'name': 'NumberOfInks', 'type': TYPES.Short},
336: {'name': 'DotRange', 'type': TYPES.Byte},
337: {'name': 'TargetPrinter', 'type': TYPES.Ascii},
338: {'name': 'ExtraSamples', 'type': TYPES.Short},
339: {'name': 'SampleFormat', 'type': TYPES.Short},
340: {'name': 'SMinSampleValue', 'type': TYPES.Short},
341: {'name': 'SMaxSampleValue', 'type': TYPES.Short},
342: {'name': 'TransferRange', 'type': TYPES.Short},
343: {'name': 'ClipPath', 'type': TYPES.Byte},
344: {'name': 'XClipPathUnits', 'type': TYPES.Long},
345: {'name': 'YClipPathUnits', 'type': TYPES.Long},
346: {'name': 'Indexed', 'type': TYPES.Short},
347: {'name': 'JPEGTables', 'type': TYPES.Undefined},
351: {'name': 'OPIProxy', 'type': TYPES.Short},
512: {'name': 'JPEGProc', 'type': TYPES.Long},
513: {'name': 'JPEGInterchangeFormat', 'type': TYPES.Long},
514: {'name': 'JPEGInterchangeFormatLength', 'type': TYPES.Long},
515: {'name': 'JPEGRestartInterval', 'type': TYPES.Short},
517: {'name': 'JPEGLosslessPredictors', 'type': TYPES.Short},
518: {'name': 'JPEGPointTransforms', 'type': TYPES.Short},
519: {'name': 'JPEGQTables', 'type': TYPES.Long},
520: {'name': 'JPEGDCTables', 'type': TYPES.Long},
521: {'name': 'JPEGACTables', 'type': TYPES.Long},
529: {'name': 'YCbCrCoefficients', 'type': TYPES.Rational},
530: {'name': 'YCbCrSubSampling', 'type': TYPES.Short},
531: {'name': 'YCbCrPositioning', 'type': TYPES.Short},
532: {'name': 'ReferenceBlackWhite', 'type': TYPES.Rational},
700: {'name': 'XMLPacket', 'type': TYPES.Byte},
18246: {'name': 'Rating', 'type': TYPES.Short},
18249: {'name': 'RatingPercent', 'type': TYPES.Short},
32781: {'name': 'ImageID', 'type': TYPES.Ascii},
33421: {'name': 'CFARepeatPatternDim', 'type': TYPES.Short},
33422: {'name': 'CFAPattern', 'type': TYPES.Byte},
33423: {'name': 'BatteryLevel', 'type': TYPES.Rational},
33432: {'name': 'Copyright', 'type': TYPES.Ascii},
33434: {'name': 'ExposureTime', 'type': TYPES.Rational},
34377: {'name': 'ImageResources', 'type': TYPES.Byte},
34665: {'name': 'ExifTag', 'type': TYPES.Long},
34675: {'name': 'InterColorProfile', 'type': TYPES.Undefined},
34853: {'name': 'GPSTag', 'type': TYPES.Long},
34857: {'name': 'Interlace', 'type': TYPES.Short},
34858: {'name': 'TimeZoneOffset', 'type': TYPES.Long},
34859: {'name': 'SelfTimerMode', 'type': TYPES.Short},
37387: {'name': 'FlashEnergy', 'type': TYPES.Rational},
37388: {'name': 'SpatialFrequencyResponse', 'type': TYPES.Undefined},
37389: {'name': 'Noise', 'type': TYPES.Undefined},
37390: {'name': 'FocalPlaneXResolution', 'type': TYPES.Rational},
37391: {'name': 'FocalPlaneYResolution', 'type': TYPES.Rational},
37392: {'name': 'FocalPlaneResolutionUnit', 'type': TYPES.Short},
37393: {'name': 'ImageNumber', 'type': TYPES.Long},
37394: {'name': 'SecurityClassification', 'type': TYPES.Ascii},
37395: {'name': 'ImageHistory', 'type': TYPES.Ascii},
37397: {'name': 'ExposureIndex', 'type': TYPES.Rational},
37398: {'name': 'TIFFEPStandardID', 'type': TYPES.Byte},
37399: {'name': 'SensingMethod', 'type': TYPES.Short},
40091: {'name': 'XPTitle', 'type': TYPES.Byte},
40092: {'name': 'XPComment', 'type': TYPES.Byte},
40093: {'name': 'XPAuthor', 'type': TYPES.Byte},
40094: {'name': 'XPKeywords', 'type': TYPES.Byte},
40095: {'name': 'XPSubject', 'type': TYPES.Byte},
50341: {'name': 'PrintImageMatching', 'type': TYPES.Undefined},
50706: {'name': 'DNGVersion', 'type': TYPES.Byte},
50707: {'name': 'DNGBackwardVersion', 'type': TYPES.Byte},
50708: {'name': 'UniqueCameraModel', 'type': TYPES.Ascii},
50709: {'name': 'LocalizedCameraModel', 'type': TYPES.Byte},
50710: {'name': 'CFAPlaneColor', 'type': TYPES.Byte},
50711: {'name': 'CFALayout', 'type': TYPES.Short},
50712: {'name': 'LinearizationTable', 'type': TYPES.Short},
50713: {'name': 'BlackLevelRepeatDim', 'type': TYPES.Short},
50714: {'name': 'BlackLevel', 'type': TYPES.Rational},
50715: {'name': 'BlackLevelDeltaH', 'type': TYPES.SRational},
50716: {'name': 'BlackLevelDeltaV', 'type': TYPES.SRational},
50717: {'name': 'WhiteLevel', 'type': TYPES.Short},
50718: {'name': 'DefaultScale', 'type': TYPES.Rational},
50719: {'name': 'DefaultCropOrigin', 'type': TYPES.Short},
50720: {'name': 'DefaultCropSize', 'type': TYPES.Short},
50721: {'name': 'ColorMatrix1', 'type': TYPES.SRational},
50722: {'name': 'ColorMatrix2', 'type': TYPES.SRational},
50723: {'name': 'CameraCalibration1', 'type': TYPES.SRational},
50724: {'name': 'CameraCalibration2', 'type': TYPES.SRational},
50725: {'name': 'ReductionMatrix1', 'type': TYPES.SRational},
50726: {'name': 'ReductionMatrix2', 'type': TYPES.SRational},
50727: {'name': 'AnalogBalance', 'type': TYPES.Rational},
50728: {'name': 'AsShotNeutral', 'type': TYPES.Short},
50729: {'name': 'AsShotWhiteXY', 'type': TYPES.Rational},
50730: {'name': 'BaselineExposure', 'type': TYPES.SRational},
50731: {'name': 'BaselineNoise', 'type': TYPES.Rational},
50732: {'name': 'BaselineSharpness', 'type': TYPES.Rational},
50733: {'name': 'BayerGreenSplit', 'type': TYPES.Long},
50734: {'name': 'LinearResponseLimit', 'type': TYPES.Rational},
50735: {'name': 'CameraSerialNumber', 'type': TYPES.Ascii},
50736: {'name': 'LensInfo', 'type': TYPES.Rational},
50737: {'name': 'ChromaBlurRadius', 'type': TYPES.Rational},
50738: {'name': 'AntiAliasStrength', 'type': TYPES.Rational},
50739: {'name': 'ShadowScale', 'type': TYPES.SRational},
50740: {'name': 'DNGPrivateData', 'type': TYPES.Byte},
50741: {'name': 'MakerNoteSafety', 'type': TYPES.Short},
50778: {'name': 'CalibrationIlluminant1', 'type': TYPES.Short},
50779: {'name': 'CalibrationIlluminant2', 'type': TYPES.Short},
50780: {'name': 'BestQualityScale', 'type': TYPES.Rational},
50781: {'name': 'RawDataUniqueID', 'type': TYPES.Byte},
50827: {'name': 'OriginalRawFileName', 'type': TYPES.Byte},
50828: {'name': 'OriginalRawFileData', 'type': TYPES.Undefined},
50829: {'name': 'ActiveArea', 'type': TYPES.Short},
50830: {'name': 'MaskedAreas', 'type': TYPES.Short},
50831: {'name': 'AsShotICCProfile', 'type': TYPES.Undefined},
50832: {'name': 'AsShotPreProfileMatrix', 'type': TYPES.SRational},
50833: {'name': 'CurrentICCProfile', 'type': TYPES.Undefined},
50834: {'name': 'CurrentPreProfileMatrix', 'type': TYPES.SRational},
50879: {'name': 'ColorimetricReference', 'type': TYPES.Short},
50931: {'name': 'CameraCalibrationSignature', 'type': TYPES.Byte},
50932: {'name': 'ProfileCalibrationSignature', 'type': TYPES.Byte},
50934: {'name': 'AsShotProfileName', 'type': TYPES.Byte},
50935: {'name': 'NoiseReductionApplied', 'type': TYPES.Rational},
50936: {'name': 'ProfileName', 'type': TYPES.Byte},
50937: {'name': 'ProfileHueSatMapDims', 'type': TYPES.Long},
50938: {'name': 'ProfileHueSatMapData1', 'type': TYPES.Float},
50939: {'name': 'ProfileHueSatMapData2', 'type': TYPES.Float},
50940: {'name': 'ProfileToneCurve', 'type': TYPES.Float},
50941: {'name': 'ProfileEmbedPolicy', 'type': TYPES.Long},
50942: {'name': 'ProfileCopyright', 'type': TYPES.Byte},
50964: {'name': 'ForwardMatrix1', 'type': TYPES.SRational},
50965: {'name': 'ForwardMatrix2', 'type': TYPES.SRational},
50966: {'name': 'PreviewApplicationName', 'type': TYPES.Byte},
50967: {'name': 'PreviewApplicationVersion', 'type': TYPES.Byte},
50968: {'name': 'PreviewSettingsName', 'type': TYPES.Byte},
50969: {'name': 'PreviewSettingsDigest', 'type': TYPES.Byte},
50970: {'name': 'PreviewColorSpace', 'type': TYPES.Long},
50971: {'name': 'PreviewDateTime', 'type': TYPES.Ascii},
50972: {'name': 'RawImageDigest', 'type': TYPES.Undefined},
50973: {'name': 'OriginalRawFileDigest', 'type': TYPES.Undefined},
50974: {'name': 'SubTileBlockSize', 'type': TYPES.Long},
50975: {'name': 'RowInterleaveFactor', 'type': TYPES.Long},
50981: {'name': 'ProfileLookTableDims', 'type': TYPES.Long},
50982: {'name': 'ProfileLookTableData', 'type': TYPES.Float},
51008: {'name': 'OpcodeList1', 'type': TYPES.Undefined},
51009: {'name': 'OpcodeList2', 'type': TYPES.Undefined},
51022: {'name': 'OpcodeList3', 'type': TYPES.Undefined},
60606: {'name': 'ZZZTestSlong1', 'type': TYPES.SLong},
60607: {'name': 'ZZZTestSlong2', 'type': TYPES.SLong},
60608: {'name': 'ZZZTestSByte', 'type': TYPES.SByte},
60609: {'name': 'ZZZTestSShort', 'type': TYPES.SShort},
60610: {'name': 'ZZZTestDFloat', 'type': TYPES.DFloat},},
'Exif': {33434: {'name': 'ExposureTime', 'type': TYPES.Rational},
33437: {'name': 'FNumber', 'type': TYPES.Rational},
34850: {'name': 'ExposureProgram', 'type': TYPES.Short},
34852: {'name': 'SpectralSensitivity', 'type': TYPES.Ascii},
34855: {'name': 'ISOSpeedRatings', 'type': TYPES.Short},
34856: {'name': 'OECF', 'type': TYPES.Undefined},
34864: {'name': 'SensitivityType', 'type': TYPES.Short},
34865: {'name': 'StandardOutputSensitivity', 'type': TYPES.Long},
34866: {'name': 'RecommendedExposureIndex', 'type': TYPES.Long},
34867: {'name': 'ISOSpeed', 'type': TYPES.Long},
34868: {'name': 'ISOSpeedLatitudeyyy', 'type': TYPES.Long},
34869: {'name': 'ISOSpeedLatitudezzz', 'type': TYPES.Long},
36864: {'name': 'ExifVersion', 'type': TYPES.Undefined},
36867: {'name': 'DateTimeOriginal', 'type': TYPES.Ascii},
36868: {'name': 'DateTimeDigitized', 'type': TYPES.Ascii},
36880: {'name': 'OffsetTime', 'type': TYPES.Ascii},
36881: {'name': 'OffsetTimeOriginal', 'type': TYPES.Ascii},
36882: {'name': 'OffsetTimeDigitized', 'type': TYPES.Ascii},
37121: {'name': 'ComponentsConfiguration', 'type': TYPES.Undefined},
37122: {'name': 'CompressedBitsPerPixel', 'type': TYPES.Rational},
37377: {'name': 'ShutterSpeedValue', 'type': TYPES.SRational},
37378: {'name': 'ApertureValue', 'type': TYPES.Rational},
37379: {'name': 'BrightnessValue', 'type': TYPES.SRational},
37380: {'name': 'ExposureBiasValue', 'type': TYPES.SRational},
37381: {'name': 'MaxApertureValue', 'type': TYPES.Rational},
37382: {'name': 'SubjectDistance', 'type': TYPES.Rational},
37383: {'name': 'MeteringMode', 'type': TYPES.Short},
37384: {'name': 'LightSource', 'type': TYPES.Short},
37385: {'name': 'Flash', 'type': TYPES.Short},
37386: {'name': 'FocalLength', 'type': TYPES.Rational},
37396: {'name': 'SubjectArea', 'type': TYPES.Short},
37500: {'name': 'MakerNote', 'type': TYPES.Undefined},
37510: {'name': 'UserComment', 'type': TYPES.Undefined},
37520: {'name': 'SubSecTime', 'type': TYPES.Ascii},
37521: {'name': 'SubSecTimeOriginal', 'type': TYPES.Ascii},
37522: {'name': 'SubSecTimeDigitized', 'type': TYPES.Ascii},
37888: {'name': 'Temperature', 'type': TYPES.SRational},
37889: {'name': 'Humidity', 'type': TYPES.Rational},
37890: {'name': 'Pressure', 'type': TYPES.Rational},
37891: {'name': 'WaterDepth', 'type': TYPES.SRational},
37892: {'name': 'Acceleration', 'type': TYPES.Rational},
37893: {'name': 'CameraElevationAngle', 'type': TYPES.SRational},
40960: {'name': 'FlashpixVersion', 'type': TYPES.Undefined},
40961: {'name': 'ColorSpace', 'type': TYPES.Short},
40962: {'name': 'PixelXDimension', 'type': TYPES.Long},
40963: {'name': 'PixelYDimension', 'type': TYPES.Long},
40964: {'name': 'RelatedSoundFile', 'type': TYPES.Ascii},
40965: {'name': 'InteroperabilityTag', 'type': TYPES.Long},
41483: {'name': 'FlashEnergy', 'type': TYPES.Rational},
41484: {'name': 'SpatialFrequencyResponse', 'type': TYPES.Undefined},
41486: {'name': 'FocalPlaneXResolution', 'type': TYPES.Rational},
41487: {'name': 'FocalPlaneYResolution', 'type': TYPES.Rational},
41488: {'name': 'FocalPlaneResolutionUnit', 'type': TYPES.Short},
41492: {'name': 'SubjectLocation', 'type': TYPES.Short},
41493: {'name': 'ExposureIndex', 'type': TYPES.Rational},
41495: {'name': 'SensingMethod', 'type': TYPES.Short},
41728: {'name': 'FileSource', 'type': TYPES.Undefined},
41729: {'name': 'SceneType', 'type': TYPES.Undefined},
41730: {'name': 'CFAPattern', 'type': TYPES.Undefined},
41985: {'name': 'CustomRendered', 'type': TYPES.Short},
41986: {'name': 'ExposureMode', 'type': TYPES.Short},
41987: {'name': 'WhiteBalance', 'type': TYPES.Short},
41988: {'name': 'DigitalZoomRatio', 'type': TYPES.Rational},
41989: {'name': 'FocalLengthIn35mmFilm', 'type': TYPES.Short},
41990: {'name': 'SceneCaptureType', 'type': TYPES.Short},
41991: {'name': 'GainControl', 'type': TYPES.Short},
41992: {'name': 'Contrast', 'type': TYPES.Short},
41993: {'name': 'Saturation', 'type': TYPES.Short},
41994: {'name': 'Sharpness', 'type': TYPES.Short},
41995: {'name': 'DeviceSettingDescription', 'type': TYPES.Undefined},
41996: {'name': 'SubjectDistanceRange', 'type': TYPES.Short},
42016: {'name': 'ImageUniqueID', 'type': TYPES.Ascii},
42032: {'name': 'CameraOwnerName', 'type': TYPES.Ascii},
42033: {'name': 'BodySerialNumber', 'type': TYPES.Ascii},
42034: {'name': 'LensSpecification', 'type': TYPES.Rational},
42035: {'name': 'LensMake', 'type': TYPES.Ascii},
42036: {'name': 'LensModel', 'type': TYPES.Ascii},
42037: {'name': 'LensSerialNumber', 'type': TYPES.Ascii},
42240: {'name': 'Gamma', 'type': TYPES.Rational}},
'GPS': {0: {'name': 'GPSVersionID', 'type': TYPES.Byte},
1: {'name': 'GPSLatitudeRef', 'type': TYPES.Ascii},
2: {'name': 'GPSLatitude', 'type': TYPES.Rational},
3: {'name': 'GPSLongitudeRef', 'type': TYPES.Ascii},
4: {'name': 'GPSLongitude', 'type': TYPES.Rational},
5: {'name': 'GPSAltitudeRef', 'type': TYPES.Byte},
6: {'name': 'GPSAltitude', 'type': TYPES.Rational},
7: {'name': 'GPSTimeStamp', 'type': TYPES.Rational},
8: {'name': 'GPSSatellites', 'type': TYPES.Ascii},
9: {'name': 'GPSStatus', 'type': TYPES.Ascii},
10: {'name': 'GPSMeasureMode', 'type': TYPES.Ascii},
11: {'name': 'GPSDOP', 'type': TYPES.Rational},
12: {'name': 'GPSSpeedRef', 'type': TYPES.Ascii},
13: {'name': 'GPSSpeed', 'type': TYPES.Rational},
14: {'name': 'GPSTrackRef', 'type': TYPES.Ascii},
15: {'name': 'GPSTrack', 'type': TYPES.Rational},
16: {'name': 'GPSImgDirectionRef', 'type': TYPES.Ascii},
17: {'name': 'GPSImgDirection', 'type': TYPES.Rational},
18: {'name': 'GPSMapDatum', 'type': TYPES.Ascii},
19: {'name': 'GPSDestLatitudeRef', 'type': TYPES.Ascii},
20: {'name': 'GPSDestLatitude', 'type': TYPES.Rational},
21: {'name': 'GPSDestLongitudeRef', 'type': TYPES.Ascii},
22: {'name': 'GPSDestLongitude', 'type': TYPES.Rational},
23: {'name': 'GPSDestBearingRef', 'type': TYPES.Ascii},
24: {'name': 'GPSDestBearing', 'type': TYPES.Rational},
25: {'name': 'GPSDestDistanceRef', 'type': TYPES.Ascii},
26: {'name': 'GPSDestDistance', 'type': TYPES.Rational},
27: {'name': 'GPSProcessingMethod', 'type': TYPES.Undefined},
28: {'name': 'GPSAreaInformation', 'type': TYPES.Undefined},
29: {'name': 'GPSDateStamp', 'type': TYPES.Ascii},
30: {'name': 'GPSDifferential', 'type': TYPES.Short},
31: {'name': 'GPSHPositioningError', 'type': TYPES.Rational}},
'Interop': {1: {'name': 'InteroperabilityIndex', 'type': TYPES.Ascii}},
"Image": {
11: {"name": "ProcessingSoftware", "type": TYPES.Ascii},
254: {"name": "NewSubfileType", "type": TYPES.Long},
255: {"name": "SubfileType", "type": TYPES.Short},
256: {"name": "ImageWidth", "type": TYPES.Long},
257: {"name": "ImageLength", "type": TYPES.Long},
258: {"name": "BitsPerSample", "type": TYPES.Short},
259: {"name": "Compression", "type": TYPES.Short},
262: {"name": "PhotometricInterpretation", "type": TYPES.Short},
263: {"name": "Threshholding", "type": TYPES.Short},
264: {"name": "CellWidth", "type": TYPES.Short},
265: {"name": "CellLength", "type": TYPES.Short},
266: {"name": "FillOrder", "type": TYPES.Short},
269: {"name": "DocumentName", "type": TYPES.Ascii},
270: {"name": "ImageDescription", "type": TYPES.Ascii},
271: {"name": "Make", "type": TYPES.Ascii},
272: {"name": "Model", "type": TYPES.Ascii},
273: {"name": "StripOffsets", "type": TYPES.Long},
274: {"name": "Orientation", "type": TYPES.Short},
277: {"name": "SamplesPerPixel", "type": TYPES.Short},
278: {"name": "RowsPerStrip", "type": TYPES.Long},
279: {"name": "StripByteCounts", "type": TYPES.Long},
282: {"name": "XResolution", "type": TYPES.Rational},
283: {"name": "YResolution", "type": TYPES.Rational},
284: {"name": "PlanarConfiguration", "type": TYPES.Short},
290: {"name": "GrayResponseUnit", "type": TYPES.Short},
291: {"name": "GrayResponseCurve", "type": TYPES.Short},
292: {"name": "T4Options", "type": TYPES.Long},
293: {"name": "T6Options", "type": TYPES.Long},
296: {"name": "ResolutionUnit", "type": TYPES.Short},
301: {"name": "TransferFunction", "type": TYPES.Short},
305: {"name": "Software", "type": TYPES.Ascii},
306: {"name": "DateTime", "type": TYPES.Ascii},
315: {"name": "Artist", "type": TYPES.Ascii},
316: {"name": "HostComputer", "type": TYPES.Ascii},
317: {"name": "Predictor", "type": TYPES.Short},
318: {"name": "WhitePoint", "type": TYPES.Rational},
319: {"name": "PrimaryChromaticities", "type": TYPES.Rational},
320: {"name": "ColorMap", "type": TYPES.Short},
321: {"name": "HalftoneHints", "type": TYPES.Short},
322: {"name": "TileWidth", "type": TYPES.Short},
323: {"name": "TileLength", "type": TYPES.Short},
324: {"name": "TileOffsets", "type": TYPES.Short},
325: {"name": "TileByteCounts", "type": TYPES.Short},
330: {"name": "SubIFDs", "type": TYPES.Long},
332: {"name": "InkSet", "type": TYPES.Short},
333: {"name": "InkNames", "type": TYPES.Ascii},
334: {"name": "NumberOfInks", "type": TYPES.Short},
336: {"name": "DotRange", "type": TYPES.Byte},
337: {"name": "TargetPrinter", "type": TYPES.Ascii},
338: {"name": "ExtraSamples", "type": TYPES.Short},
339: {"name": "SampleFormat", "type": TYPES.Short},
340: {"name": "SMinSampleValue", "type": TYPES.Short},
341: {"name": "SMaxSampleValue", "type": TYPES.Short},
342: {"name": "TransferRange", "type": TYPES.Short},
343: {"name": "ClipPath", "type": TYPES.Byte},
344: {"name": "XClipPathUnits", "type": TYPES.Long},
345: {"name": "YClipPathUnits", "type": TYPES.Long},
346: {"name": "Indexed", "type": TYPES.Short},
347: {"name": "JPEGTables", "type": TYPES.Undefined},
351: {"name": "OPIProxy", "type": TYPES.Short},
512: {"name": "JPEGProc", "type": TYPES.Long},
513: {"name": "JPEGInterchangeFormat", "type": TYPES.Long},
514: {"name": "JPEGInterchangeFormatLength", "type": TYPES.Long},
515: {"name": "JPEGRestartInterval", "type": TYPES.Short},
517: {"name": "JPEGLosslessPredictors", "type": TYPES.Short},
518: {"name": "JPEGPointTransforms", "type": TYPES.Short},
519: {"name": "JPEGQTables", "type": TYPES.Long},
520: {"name": "JPEGDCTables", "type": TYPES.Long},
521: {"name": "JPEGACTables", "type": TYPES.Long},
529: {"name": "YCbCrCoefficients", "type": TYPES.Rational},
530: {"name": "YCbCrSubSampling", "type": TYPES.Short},
531: {"name": "YCbCrPositioning", "type": TYPES.Short},
532: {"name": "ReferenceBlackWhite", "type": TYPES.Rational},
700: {"name": "XMLPacket", "type": TYPES.Byte},
18246: {"name": "Rating", "type": TYPES.Short},
18249: {"name": "RatingPercent", "type": TYPES.Short},
32781: {"name": "ImageID", "type": TYPES.Ascii},
33421: {"name": "CFARepeatPatternDim", "type": TYPES.Short},
33422: {"name": "CFAPattern", "type": TYPES.Byte},
33423: {"name": "BatteryLevel", "type": TYPES.Rational},
33432: {"name": "Copyright", "type": TYPES.Ascii},
33434: {"name": "ExposureTime", "type": TYPES.Rational},
34377: {"name": "ImageResources", "type": TYPES.Byte},
34665: {"name": "ExifTag", "type": TYPES.Long},
34675: {"name": "InterColorProfile", "type": TYPES.Undefined},
34853: {"name": "GPSTag", "type": TYPES.Long},
34857: {"name": "Interlace", "type": TYPES.Short},
34858: {"name": "TimeZoneOffset", "type": TYPES.Long},
34859: {"name": "SelfTimerMode", "type": TYPES.Short},
37387: {"name": "FlashEnergy", "type": TYPES.Rational},
37388: {"name": "SpatialFrequencyResponse", "type": TYPES.Undefined},
37389: {"name": "Noise", "type": TYPES.Undefined},
37390: {"name": "FocalPlaneXResolution", "type": TYPES.Rational},
37391: {"name": "FocalPlaneYResolution", "type": TYPES.Rational},
37392: {"name": "FocalPlaneResolutionUnit", "type": TYPES.Short},
37393: {"name": "ImageNumber", "type": TYPES.Long},
37394: {"name": "SecurityClassification", "type": TYPES.Ascii},
37395: {"name": "ImageHistory", "type": TYPES.Ascii},
37397: {"name": "ExposureIndex", "type": TYPES.Rational},
37398: {"name": "TIFFEPStandardID", "type": TYPES.Byte},
37399: {"name": "SensingMethod", "type": TYPES.Short},
40091: {"name": "XPTitle", "type": TYPES.Byte},
40092: {"name": "XPComment", "type": TYPES.Byte},
40093: {"name": "XPAuthor", "type": TYPES.Byte},
40094: {"name": "XPKeywords", "type": TYPES.Byte},
40095: {"name": "XPSubject", "type": TYPES.Byte},
50341: {"name": "PrintImageMatching", "type": TYPES.Undefined},
50706: {"name": "DNGVersion", "type": TYPES.Byte},
50707: {"name": "DNGBackwardVersion", "type": TYPES.Byte},
50708: {"name": "UniqueCameraModel", "type": TYPES.Ascii},
50709: {"name": "LocalizedCameraModel", "type": TYPES.Byte},
50710: {"name": "CFAPlaneColor", "type": TYPES.Byte},
50711: {"name": "CFALayout", "type": TYPES.Short},
50712: {"name": "LinearizationTable", "type": TYPES.Short},
50713: {"name": "BlackLevelRepeatDim", "type": TYPES.Short},
50714: {"name": "BlackLevel", "type": TYPES.Rational},
50715: {"name": "BlackLevelDeltaH", "type": TYPES.SRational},
50716: {"name": "BlackLevelDeltaV", "type": TYPES.SRational},
50717: {"name": "WhiteLevel", "type": TYPES.Short},
50718: {"name": "DefaultScale", "type": TYPES.Rational},
50719: {"name": "DefaultCropOrigin", "type": TYPES.Short},
50720: {"name": "DefaultCropSize", "type": TYPES.Short},
50721: {"name": "ColorMatrix1", "type": TYPES.SRational},
50722: {"name": "ColorMatrix2", "type": TYPES.SRational},
50723: {"name": "CameraCalibration1", "type": TYPES.SRational},
50724: {"name": "CameraCalibration2", "type": TYPES.SRational},
50725: {"name": "ReductionMatrix1", "type": TYPES.SRational},
50726: {"name": "ReductionMatrix2", "type": TYPES.SRational},
50727: {"name": "AnalogBalance", "type": TYPES.Rational},
50728: {"name": "AsShotNeutral", "type": TYPES.Short},
50729: {"name": "AsShotWhiteXY", "type": TYPES.Rational},
50730: {"name": "BaselineExposure", "type": TYPES.SRational},
50731: {"name": "BaselineNoise", "type": TYPES.Rational},
50732: {"name": "BaselineSharpness", "type": TYPES.Rational},
50733: {"name": "BayerGreenSplit", "type": TYPES.Long},
50734: {"name": "LinearResponseLimit", "type": TYPES.Rational},
50735: {"name": "CameraSerialNumber", "type": TYPES.Ascii},
50736: {"name": "LensInfo", "type": TYPES.Rational},
50737: {"name": "ChromaBlurRadius", "type": TYPES.Rational},
50738: {"name": "AntiAliasStrength", "type": TYPES.Rational},
50739: {"name": "ShadowScale", "type": TYPES.SRational},
50740: {"name": "DNGPrivateData", "type": TYPES.Byte},
50741: {"name": "MakerNoteSafety", "type": TYPES.Short},
50778: {"name": "CalibrationIlluminant1", "type": TYPES.Short},
50779: {"name": "CalibrationIlluminant2", "type": TYPES.Short},
50780: {"name": "BestQualityScale", "type": TYPES.Rational},
50781: {"name": "RawDataUniqueID", "type": TYPES.Byte},
50827: {"name": "OriginalRawFileName", "type": TYPES.Byte},
50828: {"name": "OriginalRawFileData", "type": TYPES.Undefined},
50829: {"name": "ActiveArea", "type": TYPES.Short},
50830: {"name": "MaskedAreas", "type": TYPES.Short},
50831: {"name": "AsShotICCProfile", "type": TYPES.Undefined},
50832: {"name": "AsShotPreProfileMatrix", "type": TYPES.SRational},
50833: {"name": "CurrentICCProfile", "type": TYPES.Undefined},
50834: {"name": "CurrentPreProfileMatrix", "type": TYPES.SRational},
50879: {"name": "ColorimetricReference", "type": TYPES.Short},
50931: {"name": "CameraCalibrationSignature", "type": TYPES.Byte},
50932: {"name": "ProfileCalibrationSignature", "type": TYPES.Byte},
50934: {"name": "AsShotProfileName", "type": TYPES.Byte},
50935: {"name": "NoiseReductionApplied", "type": TYPES.Rational},
50936: {"name": "ProfileName", "type": TYPES.Byte},
50937: {"name": "ProfileHueSatMapDims", "type": TYPES.Long},
50938: {"name": "ProfileHueSatMapData1", "type": TYPES.Float},
50939: {"name": "ProfileHueSatMapData2", "type": TYPES.Float},
50940: {"name": "ProfileToneCurve", "type": TYPES.Float},
50941: {"name": "ProfileEmbedPolicy", "type": TYPES.Long},
50942: {"name": "ProfileCopyright", "type": TYPES.Byte},
50964: {"name": "ForwardMatrix1", "type": TYPES.SRational},
50965: {"name": "ForwardMatrix2", "type": TYPES.SRational},
50966: {"name": "PreviewApplicationName", "type": TYPES.Byte},
50967: {"name": "PreviewApplicationVersion", "type": TYPES.Byte},
50968: {"name": "PreviewSettingsName", "type": TYPES.Byte},
50969: {"name": "PreviewSettingsDigest", "type": TYPES.Byte},
50970: {"name": "PreviewColorSpace", "type": TYPES.Long},
50971: {"name": "PreviewDateTime", "type": TYPES.Ascii},
50972: {"name": "RawImageDigest", "type": TYPES.Undefined},
50973: {"name": "OriginalRawFileDigest", "type": TYPES.Undefined},
50974: {"name": "SubTileBlockSize", "type": TYPES.Long},
50975: {"name": "RowInterleaveFactor", "type": TYPES.Long},
50981: {"name": "ProfileLookTableDims", "type": TYPES.Long},
50982: {"name": "ProfileLookTableData", "type": TYPES.Float},
51008: {"name": "OpcodeList1", "type": TYPES.Undefined},
51009: {"name": "OpcodeList2", "type": TYPES.Undefined},
51022: {"name": "OpcodeList3", "type": TYPES.Undefined},
60606: {"name": "ZZZTestSlong1", "type": TYPES.SLong},
60607: {"name": "ZZZTestSlong2", "type": TYPES.SLong},
60608: {"name": "ZZZTestSByte", "type": TYPES.SByte},
60609: {"name": "ZZZTestSShort", "type": TYPES.SShort},
60610: {"name": "ZZZTestDFloat", "type": TYPES.DFloat},
},
"Exif": {
33434: {"name": "ExposureTime", "type": TYPES.Rational},
33437: {"name": "FNumber", "type": TYPES.Rational},
34850: {"name": "ExposureProgram", "type": TYPES.Short},
34852: {"name": "SpectralSensitivity", "type": TYPES.Ascii},
34855: {"name": "ISOSpeedRatings", "type": TYPES.Short},
34856: {"name": "OECF", "type": TYPES.Undefined},
34864: {"name": "SensitivityType", "type": TYPES.Short},
34865: {"name": "StandardOutputSensitivity", "type": TYPES.Long},
34866: {"name": "RecommendedExposureIndex", "type": TYPES.Long},
34867: {"name": "ISOSpeed", "type": TYPES.Long},
34868: {"name": "ISOSpeedLatitudeyyy", "type": TYPES.Long},
34869: {"name": "ISOSpeedLatitudezzz", "type": TYPES.Long},
36864: {"name": "ExifVersion", "type": TYPES.Undefined},
36867: {"name": "DateTimeOriginal", "type": TYPES.Ascii},
36868: {"name": "DateTimeDigitized", "type": TYPES.Ascii},
36880: {"name": "OffsetTime", "type": TYPES.Ascii},
36881: {"name": "OffsetTimeOriginal", "type": TYPES.Ascii},
36882: {"name": "OffsetTimeDigitized", "type": TYPES.Ascii},
37121: {"name": "ComponentsConfiguration", "type": TYPES.Undefined},
37122: {"name": "CompressedBitsPerPixel", "type": TYPES.Rational},
37377: {"name": "ShutterSpeedValue", "type": TYPES.SRational},
37378: {"name": "ApertureValue", "type": TYPES.Rational},
37379: {"name": "BrightnessValue", "type": TYPES.SRational},
37380: {"name": "ExposureBiasValue", "type": TYPES.SRational},
37381: {"name": "MaxApertureValue", "type": TYPES.Rational},
37382: {"name": "SubjectDistance", "type": TYPES.Rational},
37383: {"name": "MeteringMode", "type": TYPES.Short},
37384: {"name": "LightSource", "type": TYPES.Short},
37385: {"name": "Flash", "type": TYPES.Short},
37386: {"name": "FocalLength", "type": TYPES.Rational},
37396: {"name": "SubjectArea", "type": TYPES.Short},
37500: {"name": "MakerNote", "type": TYPES.Undefined},
37510: {"name": "UserComment", "type": TYPES.Undefined},
37520: {"name": "SubSecTime", "type": TYPES.Ascii},
37521: {"name": "SubSecTimeOriginal", "type": TYPES.Ascii},
37522: {"name": "SubSecTimeDigitized", "type": TYPES.Ascii},
37888: {"name": "Temperature", "type": TYPES.SRational},
37889: {"name": "Humidity", "type": TYPES.Rational},
37890: {"name": "Pressure", "type": TYPES.Rational},
37891: {"name": "WaterDepth", "type": TYPES.SRational},
37892: {"name": "Acceleration", "type": TYPES.Rational},
37893: {"name": "CameraElevationAngle", "type": TYPES.SRational},
40960: {"name": "FlashpixVersion", "type": TYPES.Undefined},
40961: {"name": "ColorSpace", "type": TYPES.Short},
40962: {"name": "PixelXDimension", "type": TYPES.Long},
40963: {"name": "PixelYDimension", "type": TYPES.Long},
40964: {"name": "RelatedSoundFile", "type": TYPES.Ascii},
40965: {"name": "InteroperabilityTag", "type": TYPES.Long},
41483: {"name": "FlashEnergy", "type": TYPES.Rational},
41484: {"name": "SpatialFrequencyResponse", "type": TYPES.Undefined},
41486: {"name": "FocalPlaneXResolution", "type": TYPES.Rational},
41487: {"name": "FocalPlaneYResolution", "type": TYPES.Rational},
41488: {"name": "FocalPlaneResolutionUnit", "type": TYPES.Short},
41492: {"name": "SubjectLocation", "type": TYPES.Short},
41493: {"name": "ExposureIndex", "type": TYPES.Rational},
41495: {"name": "SensingMethod", "type": TYPES.Short},
41728: {"name": "FileSource", "type": TYPES.Undefined},
41729: {"name": "SceneType", "type": TYPES.Undefined},
41730: {"name": "CFAPattern", "type": TYPES.Undefined},
41985: {"name": "CustomRendered", "type": TYPES.Short},
41986: {"name": "ExposureMode", "type": TYPES.Short},
41987: {"name": "WhiteBalance", "type": TYPES.Short},
41988: {"name": "DigitalZoomRatio", "type": TYPES.Rational},
41989: {"name": "FocalLengthIn35mmFilm", "type": TYPES.Short},
41990: {"name": "SceneCaptureType", "type": TYPES.Short},
41991: {"name": "GainControl", "type": TYPES.Short},
41992: {"name": "Contrast", "type": TYPES.Short},
41993: {"name": "Saturation", "type": TYPES.Short},
41994: {"name": "Sharpness", "type": TYPES.Short},
41995: {"name": "DeviceSettingDescription", "type": TYPES.Undefined},
41996: {"name": "SubjectDistanceRange", "type": TYPES.Short},
42016: {"name": "ImageUniqueID", "type": TYPES.Ascii},
42032: {"name": "CameraOwnerName", "type": TYPES.Ascii},
42033: {"name": "BodySerialNumber", "type": TYPES.Ascii},
42034: {"name": "LensSpecification", "type": TYPES.Rational},
42035: {"name": "LensMake", "type": TYPES.Ascii},
42036: {"name": "LensModel", "type": TYPES.Ascii},
42037: {"name": "LensSerialNumber", "type": TYPES.Ascii},
42240: {"name": "Gamma", "type": TYPES.Rational},
},
"GPS": {
0: {"name": "GPSVersionID", "type": TYPES.Byte},
1: {"name": "GPSLatitudeRef", "type": TYPES.Ascii},
2: {"name": "GPSLatitude", "type": TYPES.Rational},
3: {"name": "GPSLongitudeRef", "type": TYPES.Ascii},
4: {"name": "GPSLongitude", "type": TYPES.Rational},
5: {"name": "GPSAltitudeRef", "type": TYPES.Byte},
6: {"name": "GPSAltitude", "type": TYPES.Rational},
7: {"name": "GPSTimeStamp", "type": TYPES.Rational},
8: {"name": "GPSSatellites", "type": TYPES.Ascii},
9: {"name": "GPSStatus", "type": TYPES.Ascii},
10: {"name": "GPSMeasureMode", "type": TYPES.Ascii},
11: {"name": "GPSDOP", "type": TYPES.Rational},
12: {"name": "GPSSpeedRef", "type": TYPES.Ascii},
13: {"name": "GPSSpeed", "type": TYPES.Rational},
14: {"name": "GPSTrackRef", "type": TYPES.Ascii},
15: {"name": "GPSTrack", "type": TYPES.Rational},
16: {"name": "GPSImgDirectionRef", "type": TYPES.Ascii},
17: {"name": "GPSImgDirection", "type": TYPES.Rational},
18: {"name": "GPSMapDatum", "type": TYPES.Ascii},
19: {"name": "GPSDestLatitudeRef", "type": TYPES.Ascii},
20: {"name": "GPSDestLatitude", "type": TYPES.Rational},
21: {"name": "GPSDestLongitudeRef", "type": TYPES.Ascii},
22: {"name": "GPSDestLongitude", "type": TYPES.Rational},
23: {"name": "GPSDestBearingRef", "type": TYPES.Ascii},
24: {"name": "GPSDestBearing", "type": TYPES.Rational},
25: {"name": "GPSDestDistanceRef", "type": TYPES.Ascii},
26: {"name": "GPSDestDistance", "type": TYPES.Rational},
27: {"name": "GPSProcessingMethod", "type": TYPES.Undefined},
28: {"name": "GPSAreaInformation", "type": TYPES.Undefined},
29: {"name": "GPSDateStamp", "type": TYPES.Ascii},
30: {"name": "GPSDifferential", "type": TYPES.Short},
31: {"name": "GPSHPositioningError", "type": TYPES.Rational},
},
"Interop": {1: {"name": "InteroperabilityIndex", "type": TYPES.Ascii}},
}
TAGS["0th"] = TAGS["Image"]
TAGS["1st"] = TAGS["Image"]
class ImageIFD:
"""Exif tag number reference - 0th IFD"""
ProcessingSoftware = 11
NewSubfileType = 254
SubfileType = 255
@ -516,6 +524,7 @@ class ImageIFD:
class ExifIFD:
"""Exif tag number reference - Exif IFD"""
ExposureTime = 33434
FNumber = 33437
ExposureProgram = 34850
@ -599,6 +608,7 @@ class ExifIFD:
class GPSIFD:
"""Exif tag number reference - GPS IFD"""
GPSVersionID = 0
GPSLatitudeRef = 1
GPSLatitude = 2
@ -635,4 +645,5 @@ class GPSIFD:
class InteropIFD:
"""Exif tag number reference - Interoperability IFD"""
InteroperabilityIndex = 1

View file

@ -6,6 +6,7 @@ from ._common import *
from ._exceptions import InvalidImageDataError
from . import _webp
def insert(exif, image, new_file=None):
"""
py:function:: piexif.insert(exif_bytes, filename)
@ -20,7 +21,7 @@ def insert(exif, image, new_file=None):
output_file = False
# Prevents "UnicodeWarning: Unicode equal comparison failed" warnings on Python 2
maybe_image = sys.version_info >= (3,0,0) or isinstance(image, str)
maybe_image = sys.version_info >= (3, 0, 0) or isinstance(image, str)
if maybe_image and image[0:2] == b"\xff\xd8":
image_data = image
@ -29,7 +30,7 @@ def insert(exif, image, new_file=None):
image_data = image
file_type = "webp"
else:
with open(image, 'rb') as f:
with open(image, "rb") as f:
image_data = f.read()
if image_data[0:2] == b"\xff\xd8":
file_type = "jpeg"
@ -57,4 +58,4 @@ def insert(exif, image, new_file=None):
with open(image, "wb+") as f:
f.write(new_data)
else:
raise ValueError("Give a 3rd argument to 'insert' to output file")
raise ValueError("Give a 3rd argument to 'insert' to output file")

View file

@ -19,12 +19,14 @@ def load(input_data, key_is_name=False):
:return: Exif data({"0th":dict, "Exif":dict, "GPS":dict, "Interop":dict, "1st":dict, "thumbnail":bytes})
:rtype: dict
"""
exif_dict = {"0th":{},
"Exif":{},
"GPS":{},
"Interop":{},
"1st":{},
"thumbnail":None}
exif_dict = {
"0th": {},
"Exif": {},
"GPS": {},
"Interop": {},
"1st": {},
"thumbnail": None,
}
exifReader = _ExifReader(input_data)
if exifReader.tiftag is None:
return exif_dict
@ -34,8 +36,7 @@ def load(input_data, key_is_name=False):
else:
exifReader.endian_mark = ">"
pointer = struct.unpack(exifReader.endian_mark + "L",
exifReader.tiftag[4:8])[0]
pointer = struct.unpack(exifReader.endian_mark + "L", exifReader.tiftag[4:8])[0]
exif_dict["0th"] = exifReader.get_ifd_dict(pointer, "0th")
first_ifd_pointer = exif_dict["0th"].pop("first_ifd_pointer")
if ImageIFD.ExifTag in exif_dict["0th"]:
@ -48,14 +49,19 @@ def load(input_data, key_is_name=False):
pointer = exif_dict["Exif"][ExifIFD.InteroperabilityTag]
exif_dict["Interop"] = exifReader.get_ifd_dict(pointer, "Interop")
if first_ifd_pointer != b"\x00\x00\x00\x00":
pointer = struct.unpack(exifReader.endian_mark + "L",
first_ifd_pointer)[0]
pointer = struct.unpack(exifReader.endian_mark + "L", first_ifd_pointer)[0]
exif_dict["1st"] = exifReader.get_ifd_dict(pointer, "1st")
if (ImageIFD.JPEGInterchangeFormat in exif_dict["1st"] and
ImageIFD.JPEGInterchangeFormatLength in exif_dict["1st"]):
end = (exif_dict["1st"][ImageIFD.JPEGInterchangeFormat] +
exif_dict["1st"][ImageIFD.JPEGInterchangeFormatLength])
thumb = exifReader.tiftag[exif_dict["1st"][ImageIFD.JPEGInterchangeFormat]:end]
if (
ImageIFD.JPEGInterchangeFormat in exif_dict["1st"]
and ImageIFD.JPEGInterchangeFormatLength in exif_dict["1st"]
):
end = (
exif_dict["1st"][ImageIFD.JPEGInterchangeFormat]
+ exif_dict["1st"][ImageIFD.JPEGInterchangeFormatLength]
)
thumb = exifReader.tiftag[
exif_dict["1st"][ImageIFD.JPEGInterchangeFormat] : end
]
exif_dict["thumbnail"] = thumb
if key_is_name:
@ -66,7 +72,7 @@ def load(input_data, key_is_name=False):
class _ExifReader(object):
def __init__(self, data):
# Prevents "UnicodeWarning: Unicode equal comparison failed" warnings on Python 2
maybe_image = sys.version_info >= (3,0,0) or isinstance(data, str)
maybe_image = sys.version_info >= (3, 0, 0) or isinstance(data, str)
if maybe_image and data[0:2] == b"\xff\xd8": # JPEG
segments = split_into_segments(data)
@ -82,7 +88,7 @@ class _ExifReader(object):
elif maybe_image and data[0:4] == b"Exif": # Exif
self.tiftag = data[6:]
else:
with open(data, 'rb') as f:
with open(data, "rb") as f:
magic_number = f.read(2)
if magic_number == b"\xff\xd8": # JPEG
app1 = read_exif_from_file(data)
@ -91,13 +97,13 @@ class _ExifReader(object):
else:
self.tiftag = None
elif magic_number in (b"\x49\x49", b"\x4d\x4d"): # TIFF
with open(data, 'rb') as f:
with open(data, "rb") as f:
self.tiftag = f.read()
else:
with open(data, 'rb') as f:
with open(data, "rb") as f:
header = f.read(12)
if header[0:4] == b"RIFF"and header[8:12] == b"WEBP":
with open(data, 'rb') as f:
if header[0:4] == b"RIFF" and header[8:12] == b"WEBP":
with open(data, "rb") as f:
file_data = f.read()
self.tiftag = _webp.get_exif(file_data)
else:
@ -105,8 +111,9 @@ class _ExifReader(object):
def get_ifd_dict(self, pointer, ifd_name, read_unknown=False):
ifd_dict = {}
tag_count = struct.unpack(self.endian_mark + "H",
self.tiftag[pointer: pointer+2])[0]
tag_count = struct.unpack(
self.endian_mark + "H", self.tiftag[pointer : pointer + 2]
)[0]
offset = pointer + 2
if ifd_name in ["0th", "1st"]:
t = "Image"
@ -115,26 +122,28 @@ class _ExifReader(object):
p_and_value = []
for x in range(tag_count):
pointer = offset + 12 * x
tag = struct.unpack(self.endian_mark + "H",
self.tiftag[pointer: pointer+2])[0]
value_type = struct.unpack(self.endian_mark + "H",
self.tiftag[pointer + 2: pointer + 4])[0]
value_num = struct.unpack(self.endian_mark + "L",
self.tiftag[pointer + 4: pointer + 8]
)[0]
value = self.tiftag[pointer+8: pointer+12]
tag = struct.unpack(
self.endian_mark + "H", self.tiftag[pointer : pointer + 2]
)[0]
value_type = struct.unpack(
self.endian_mark + "H", self.tiftag[pointer + 2 : pointer + 4]
)[0]
value_num = struct.unpack(
self.endian_mark + "L", self.tiftag[pointer + 4 : pointer + 8]
)[0]
value = self.tiftag[pointer + 8 : pointer + 12]
p_and_value.append((pointer, value_type, value_num, value))
v_set = (value_type, value_num, value, tag)
if tag in TAGS[t]:
ifd_dict[tag] = self.convert_value(v_set)
elif read_unknown:
ifd_dict[tag] = (v_set[0], v_set[1], v_set[2], self.tiftag)
#else:
# else:
# pass
if ifd_name == "0th":
pointer = offset + 12 * tag_count
ifd_dict["first_ifd_pointer"] = self.tiftag[pointer:pointer + 4]
ifd_dict["first_ifd_pointer"] = self.tiftag[pointer : pointer + 4]
return ifd_dict
def convert_value(self, val):
@ -143,114 +152,148 @@ class _ExifReader(object):
length = val[1]
value = val[2]
if t == TYPES.Byte: # BYTE
if t == TYPES.Byte: # BYTE
if length > 4:
pointer = struct.unpack(self.endian_mark + "L", value)[0]
data = struct.unpack("B" * length,
self.tiftag[pointer: pointer + length])
data = struct.unpack(
"B" * length, self.tiftag[pointer : pointer + length]
)
else:
data = struct.unpack("B" * length, value[0:length])
elif t == TYPES.Ascii: # ASCII
elif t == TYPES.Ascii: # ASCII
if length > 4:
pointer = struct.unpack(self.endian_mark + "L", value)[0]
data = self.tiftag[pointer: pointer+length - 1]
data = self.tiftag[pointer : pointer + length - 1]
else:
data = value[0: length - 1]
elif t == TYPES.Short: # SHORT
data = value[0 : length - 1]
elif t == TYPES.Short: # SHORT
if length > 2:
pointer = struct.unpack(self.endian_mark + "L", value)[0]
data = struct.unpack(self.endian_mark + "H" * length,
self.tiftag[pointer: pointer+length*2])
data = struct.unpack(
self.endian_mark + "H" * length,
self.tiftag[pointer : pointer + length * 2],
)
else:
data = struct.unpack(self.endian_mark + "H" * length,
value[0:length * 2])
elif t == TYPES.Long: # LONG
data = struct.unpack(
self.endian_mark + "H" * length, value[0 : length * 2]
)
elif t == TYPES.Long: # LONG
if length > 1:
pointer = struct.unpack(self.endian_mark + "L", value)[0]
data = struct.unpack(self.endian_mark + "L" * length,
self.tiftag[pointer: pointer+length*4])
data = struct.unpack(
self.endian_mark + "L" * length,
self.tiftag[pointer : pointer + length * 4],
)
else:
data = struct.unpack(self.endian_mark + "L" * length,
value)
elif t == TYPES.Rational: # RATIONAL
data = struct.unpack(self.endian_mark + "L" * length, value)
elif t == TYPES.Rational: # RATIONAL
pointer = struct.unpack(self.endian_mark + "L", value)[0]
if length > 1:
data = tuple(
(struct.unpack(self.endian_mark + "L",
self.tiftag[pointer + x * 8:
pointer + 4 + x * 8])[0],
struct.unpack(self.endian_mark + "L",
self.tiftag[pointer + 4 + x * 8:
pointer + 8 + x * 8])[0])
(
struct.unpack(
self.endian_mark + "L",
self.tiftag[pointer + x * 8 : pointer + 4 + x * 8],
)[0],
struct.unpack(
self.endian_mark + "L",
self.tiftag[pointer + 4 + x * 8 : pointer + 8 + x * 8],
)[0],
)
for x in range(length)
)
else:
data = (struct.unpack(self.endian_mark + "L",
self.tiftag[pointer: pointer + 4])[0],
struct.unpack(self.endian_mark + "L",
self.tiftag[pointer + 4: pointer + 8]
)[0])
elif t == TYPES.SByte: # SIGNED BYTES
data = (
struct.unpack(
self.endian_mark + "L", self.tiftag[pointer : pointer + 4]
)[0],
struct.unpack(
self.endian_mark + "L", self.tiftag[pointer + 4 : pointer + 8]
)[0],
)
elif t == TYPES.SByte: # SIGNED BYTES
if length > 4:
pointer = struct.unpack(self.endian_mark + "L", value)[0]
data = struct.unpack("b" * length,
self.tiftag[pointer: pointer + length])
data = struct.unpack(
"b" * length, self.tiftag[pointer : pointer + length]
)
else:
data = struct.unpack("b" * length, value[0:length])
elif t == TYPES.Undefined: # UNDEFINED BYTES
elif t == TYPES.Undefined: # UNDEFINED BYTES
if length > 4:
pointer = struct.unpack(self.endian_mark + "L", value)[0]
data = self.tiftag[pointer: pointer+length]
data = self.tiftag[pointer : pointer + length]
else:
data = value[0:length]
elif t == TYPES.SShort: # SIGNED SHORT
elif t == TYPES.SShort: # SIGNED SHORT
if length > 2:
pointer = struct.unpack(self.endian_mark + "L", value)[0]
data = struct.unpack(self.endian_mark + "h" * length,
self.tiftag[pointer: pointer+length*2])
data = struct.unpack(
self.endian_mark + "h" * length,
self.tiftag[pointer : pointer + length * 2],
)
else:
data = struct.unpack(self.endian_mark + "h" * length,
value[0:length * 2])
elif t == TYPES.SLong: # SLONG
data = struct.unpack(
self.endian_mark + "h" * length, value[0 : length * 2]
)
elif t == TYPES.SLong: # SLONG
if length > 1:
pointer = struct.unpack(self.endian_mark + "L", value)[0]
data = struct.unpack(self.endian_mark + "l" * length,
self.tiftag[pointer: pointer+length*4])
data = struct.unpack(
self.endian_mark + "l" * length,
self.tiftag[pointer : pointer + length * 4],
)
else:
data = struct.unpack(self.endian_mark + "l" * length,
value)
elif t == TYPES.SRational: # SRATIONAL
data = struct.unpack(self.endian_mark + "l" * length, value)
elif t == TYPES.SRational: # SRATIONAL
pointer = struct.unpack(self.endian_mark + "L", value)[0]
if length > 1:
data = tuple(
(struct.unpack(self.endian_mark + "l",
self.tiftag[pointer + x * 8: pointer + 4 + x * 8])[0],
struct.unpack(self.endian_mark + "l",
self.tiftag[pointer + 4 + x * 8: pointer + 8 + x * 8])[0])
for x in range(length)
(
struct.unpack(
self.endian_mark + "l",
self.tiftag[pointer + x * 8 : pointer + 4 + x * 8],
)[0],
struct.unpack(
self.endian_mark + "l",
self.tiftag[pointer + 4 + x * 8 : pointer + 8 + x * 8],
)[0],
)
for x in range(length)
)
else:
data = (struct.unpack(self.endian_mark + "l",
self.tiftag[pointer: pointer + 4])[0],
struct.unpack(self.endian_mark + "l",
self.tiftag[pointer + 4: pointer + 8]
)[0])
elif t == TYPES.Float: # FLOAT
data = (
struct.unpack(
self.endian_mark + "l", self.tiftag[pointer : pointer + 4]
)[0],
struct.unpack(
self.endian_mark + "l", self.tiftag[pointer + 4 : pointer + 8]
)[0],
)
elif t == TYPES.Float: # FLOAT
if length > 1:
pointer = struct.unpack(self.endian_mark + "L", value)[0]
data = struct.unpack(self.endian_mark + "f" * length,
self.tiftag[pointer: pointer+length*4])
data = struct.unpack(
self.endian_mark + "f" * length,
self.tiftag[pointer : pointer + length * 4],
)
else:
data = struct.unpack(self.endian_mark + "f" * length,
value)
elif t == TYPES.DFloat: # DOUBLE
data = struct.unpack(self.endian_mark + "f" * length, value)
elif t == TYPES.DFloat: # DOUBLE
pointer = struct.unpack(self.endian_mark + "L", value)[0]
data = struct.unpack(self.endian_mark + "d" * length,
self.tiftag[pointer: pointer+length*8])
data = struct.unpack(
self.endian_mark + "d" * length,
self.tiftag[pointer : pointer + length * 8],
)
else:
raise ValueError("Exif might be wrong. Got incorrect value " +
"type to decode.\n" +
"tag: " + str(val[3]) + "\ntype: " + str(t))
raise ValueError(
"Exif might be wrong. Got incorrect value "
+ "type to decode.\n"
+ "tag: "
+ str(val[3])
+ "\ntype: "
+ str(t)
)
if isinstance(data, tuple) and (len(data) == 1):
return data[0]
@ -260,11 +303,20 @@ class _ExifReader(object):
def _get_key_name_dict(exif_dict):
new_dict = {
"0th":{TAGS["Image"][n]["name"]:value for n, value in exif_dict["0th"].items()},
"Exif":{TAGS["Exif"][n]["name"]:value for n, value in exif_dict["Exif"].items()},
"1st":{TAGS["Image"][n]["name"]:value for n, value in exif_dict["1st"].items()},
"GPS":{TAGS["GPS"][n]["name"]:value for n, value in exif_dict["GPS"].items()},
"Interop":{TAGS["Interop"][n]["name"]:value for n, value in exif_dict["Interop"].items()},
"thumbnail":exif_dict["thumbnail"],
"0th": {
TAGS["Image"][n]["name"]: value for n, value in exif_dict["0th"].items()
},
"Exif": {
TAGS["Exif"][n]["name"]: value for n, value in exif_dict["Exif"].items()
},
"1st": {
TAGS["Image"][n]["name"]: value for n, value in exif_dict["1st"].items()
},
"GPS": {TAGS["GPS"][n]["name"]: value for n, value in exif_dict["GPS"].items()},
"Interop": {
TAGS["Interop"][n]["name"]: value
for n, value in exif_dict["Interop"].items()
},
"thumbnail": exif_dict["thumbnail"],
}
return new_dict
return new_dict

View file

@ -3,6 +3,7 @@ import io
from ._common import *
from . import _webp
def remove(src, new_file=None):
"""
py:function:: piexif.remove(filename)
@ -19,7 +20,7 @@ def remove(src, new_file=None):
src_data = src
file_type = "webp"
else:
with open(src, 'rb') as f:
with open(src, "rb") as f:
src_data = f.read()
output_is_file = True
if src_data[0:2] == b"\xff\xd8":
@ -53,4 +54,4 @@ def remove(src, new_file=None):
with open(src, "wb+") as f:
f.write(new_data)
else:
raise ValueError("Give a second argument to 'remove' to output file")
raise ValueError("Give a second argument to 'remove' to output file")

View file

@ -15,7 +15,7 @@ def transplant(exif_src, image, new_file=None):
if exif_src[0:2] == b"\xff\xd8":
src_data = exif_src
else:
with open(exif_src, 'rb') as f:
with open(exif_src, "rb") as f:
src_data = f.read()
segments = split_into_segments(src_data)
exif = get_exif_seg(segments)
@ -26,7 +26,7 @@ def transplant(exif_src, image, new_file=None):
if image[0:2] == b"\xff\xd8":
image_data = image
else:
with open(image, 'rb') as f:
with open(image, "rb") as f:
image_data = f.read()
output_file = True
segments = split_into_segments(image_data)
@ -42,4 +42,4 @@ def transplant(exif_src, image, new_file=None):
with open(image, "wb+") as f:
f.write(new_data)
else:
raise ValueError("Give a 3rd argument to 'transplant' to output file")
raise ValueError("Give a 3rd argument to 'transplant' to output file")

View file

@ -1,4 +1,3 @@
import struct
@ -18,26 +17,33 @@ def split(data):
chunks = []
while pointer + CHUNK_FOURCC_LENGTH + LENGTH_BYTES_LENGTH < file_size:
fourcc = data[pointer:pointer + CHUNK_FOURCC_LENGTH]
fourcc = data[pointer : pointer + CHUNK_FOURCC_LENGTH]
pointer += CHUNK_FOURCC_LENGTH
chunk_length_bytes = data[pointer:pointer + LENGTH_BYTES_LENGTH]
chunk_length_bytes = data[pointer : pointer + LENGTH_BYTES_LENGTH]
chunk_length = struct.unpack("<L", chunk_length_bytes)[0]
pointer += LENGTH_BYTES_LENGTH
chunk_data = data[pointer:pointer + chunk_length]
chunks.append({"fourcc":fourcc, "length_bytes":chunk_length_bytes, "data":chunk_data})
chunk_data = data[pointer : pointer + chunk_length]
chunks.append(
{"fourcc": fourcc, "length_bytes": chunk_length_bytes, "data": chunk_data}
)
padding = 1 if chunk_length % 2 else 0
pointer += chunk_length + padding
return chunks
def merge_chunks(chunks):
merged = b"".join([chunk["fourcc"]
+ chunk["length_bytes"]
+ chunk["data"]
+ (len(chunk["data"]) % 2) * b"\x00"
for chunk in chunks])
merged = b"".join(
[
chunk["fourcc"]
+ chunk["length_bytes"]
+ chunk["data"]
+ (len(chunk["data"]) % 2) * b"\x00"
for chunk in chunks
]
)
return merged
@ -50,6 +56,7 @@ def _get_size_from_vp8x(chunk):
height = height_minus_one + 1
return (width, height)
def _get_size_from_vp8(chunk):
BEGIN_CODE = b"\x9d\x01\x2a"
begin_index = chunk["data"].find(BEGIN_CODE)
@ -59,17 +66,21 @@ def _get_size_from_vp8(chunk):
BEGIN_CODE_LENGTH = len(BEGIN_CODE)
LENGTH_BYTES_LENGTH = 2
length_start = begin_index + BEGIN_CODE_LENGTH
width_bytes = chunk["data"][length_start:length_start + LENGTH_BYTES_LENGTH]
width_bytes = chunk["data"][length_start : length_start + LENGTH_BYTES_LENGTH]
width = struct.unpack("<H", width_bytes)[0]
height_bytes = chunk["data"][length_start + LENGTH_BYTES_LENGTH:length_start + 2 * LENGTH_BYTES_LENGTH]
height_bytes = chunk["data"][
length_start + LENGTH_BYTES_LENGTH : length_start + 2 * LENGTH_BYTES_LENGTH
]
height = struct.unpack("<H", height_bytes)[0]
return (width, height)
def _vp8L_contains_alpha(chunk_data):
flag = ord(chunk_data[4:5]) >> 5-1 & ord(b"\x01")
flag = ord(chunk_data[4:5]) >> 5 - 1 & ord(b"\x01")
contains = 1 * flag
return contains
def _get_size_from_vp8L(chunk):
b1 = chunk["data"][1:2]
b2 = chunk["data"][2:3]
@ -79,11 +90,14 @@ def _get_size_from_vp8L(chunk):
width_minus_one = (ord(b2) & ord(b"\x3F")) << 8 | ord(b1)
width = width_minus_one + 1
height_minus_one = (ord(b4) & ord(b"\x0F")) << 10 | ord(b3) << 2 | (ord(b2) & ord(b"\xC0")) >> 6
height_minus_one = (
(ord(b4) & ord(b"\x0F")) << 10 | ord(b3) << 2 | (ord(b2) & ord(b"\xC0")) >> 6
)
height = height_minus_one + 1
return (width, height)
def _get_size_from_anmf(chunk):
width_minus_one_bytes = chunk["data"][6:9] + b"\x00"
width_minus_one = struct.unpack("<L", width_minus_one_bytes)[0]
@ -92,12 +106,22 @@ def _get_size_from_anmf(chunk):
height_minus_one = struct.unpack("<L", height_minus_one_bytes)[0]
height = height_minus_one + 1
return (width, height)
def set_vp8x(chunks):
width = None
height = None
flags = [b"0", b"0", b"0", b"0", b"0", b"0", b"0", b"0"] # [0, 0, ICC, Alpha, EXIF, XMP, Anim, 0]
flags = [
b"0",
b"0",
b"0",
b"0",
b"0",
b"0",
b"0",
b"0",
] # [0, 0, ICC, Alpha, EXIF, XMP, Anim, 0]
for chunk in chunks:
if chunk["fourcc"] == b"VP8X":
@ -136,11 +160,16 @@ def set_vp8x(chunks):
data_bytes = flags_bytes + padding_bytes + width_bytes + height_bytes
vp8x_chunk = {"fourcc":header_bytes, "length_bytes":length_bytes, "data":data_bytes}
vp8x_chunk = {
"fourcc": header_bytes,
"length_bytes": length_bytes,
"data": data_bytes,
}
chunks.insert(0, vp8x_chunk)
return chunks
def get_file_header(chunks):
WEBP_HEADER_LENGTH = 4
FOURCC_LENGTH = 4
@ -158,7 +187,6 @@ def get_file_header(chunks):
return file_header
def get_exif(data):
if data[0:4] != b"RIFF" or data[8:12] != b"WEBP":
raise ValueError("Not WebP")
@ -179,15 +207,15 @@ def get_exif(data):
chunks = []
exif = b""
while pointer < file_size:
fourcc = data[pointer:pointer + CHUNK_FOURCC_LENGTH]
fourcc = data[pointer : pointer + CHUNK_FOURCC_LENGTH]
pointer += CHUNK_FOURCC_LENGTH
chunk_length_bytes = data[pointer:pointer + LENGTH_BYTES_LENGTH]
chunk_length_bytes = data[pointer : pointer + LENGTH_BYTES_LENGTH]
chunk_length = struct.unpack("<L", chunk_length_bytes)[0]
if chunk_length % 2:
chunk_length += 1
pointer += LENGTH_BYTES_LENGTH
if fourcc == b"EXIF":
return data[pointer:pointer + chunk_length]
return data[pointer : pointer + chunk_length]
pointer += chunk_length
return None # if there isn't exif, return None.
@ -195,7 +223,11 @@ def get_exif(data):
def insert_exif_into_chunks(chunks, exif_bytes):
EXIF_HEADER = b"EXIF"
exif_length_bytes = struct.pack("<L", len(exif_bytes))
exif_chunk = {"fourcc":EXIF_HEADER, "length_bytes":exif_length_bytes, "data":exif_bytes}
exif_chunk = {
"fourcc": EXIF_HEADER,
"length_bytes": exif_length_bytes,
"data": exif_bytes,
}
xmp_index = None
animation_index = None

View file

@ -2,26 +2,26 @@ class UserComment:
#
# Names of encodings that we publicly support.
#
ASCII = 'ascii'
JIS = 'jis'
UNICODE = 'unicode'
ASCII = "ascii"
JIS = "jis"
UNICODE = "unicode"
ENCODINGS = (ASCII, JIS, UNICODE)
#
# The actual encodings accepted by the standard library differ slightly from
# the above.
#
_JIS = 'shift_jis'
_UNICODE = 'utf_16_be'
_JIS = "shift_jis"
_UNICODE = "utf_16_be"
_PREFIX_SIZE = 8
#
# From Table 9: Character Codes and their Designation
#
_ASCII_PREFIX = b'\x41\x53\x43\x49\x49\x00\x00\x00'
_JIS_PREFIX = b'\x4a\x49\x53\x00\x00\x00\x00\x00'
_UNICODE_PREFIX = b'\x55\x4e\x49\x43\x4f\x44\x45\x00'
_UNDEFINED_PREFIX = b'\x00\x00\x00\x00\x00\x00\x00\x00'
_ASCII_PREFIX = b"\x41\x53\x43\x49\x49\x00\x00\x00"
_JIS_PREFIX = b"\x4a\x49\x53\x00\x00\x00\x00\x00"
_UNICODE_PREFIX = b"\x55\x4e\x49\x43\x4f\x44\x45\x00"
_UNDEFINED_PREFIX = b"\x00\x00\x00\x00\x00\x00\x00\x00"
@classmethod
def load(cls, data):
@ -35,18 +35,20 @@ class UserComment:
or the encoding is unsupported.
"""
if len(data) < cls._PREFIX_SIZE:
raise ValueError('not enough data to decode UserComment')
prefix = data[:cls._PREFIX_SIZE]
body = data[cls._PREFIX_SIZE:]
raise ValueError("not enough data to decode UserComment")
prefix = data[: cls._PREFIX_SIZE]
body = data[cls._PREFIX_SIZE :]
if prefix == cls._UNDEFINED_PREFIX:
raise ValueError('prefix is UNDEFINED, unable to decode UserComment')
raise ValueError("prefix is UNDEFINED, unable to decode UserComment")
try:
encoding = {
cls._ASCII_PREFIX: cls.ASCII, cls._JIS_PREFIX: cls._JIS, cls._UNICODE_PREFIX: cls._UNICODE,
cls._ASCII_PREFIX: cls.ASCII,
cls._JIS_PREFIX: cls._JIS,
cls._UNICODE_PREFIX: cls._UNICODE,
}[prefix]
except KeyError:
raise ValueError('unable to determine appropriate encoding')
return body.decode(encoding, errors='replace')
raise ValueError("unable to determine appropriate encoding")
return body.decode(encoding, errors="replace")
@classmethod
def dump(cls, data, encoding="ascii"):
@ -60,7 +62,15 @@ class UserComment:
:raises: ValueError if the encoding is unsupported.
"""
if encoding not in cls.ENCODINGS:
raise ValueError('encoding %r must be one of %r' % (encoding, cls.ENCODINGS))
prefix = {cls.ASCII: cls._ASCII_PREFIX, cls.JIS: cls._JIS_PREFIX, cls.UNICODE: cls._UNICODE_PREFIX}[encoding]
internal_encoding = {cls.UNICODE: cls._UNICODE, cls.JIS: cls._JIS}.get(encoding, encoding)
return prefix + data.encode(internal_encoding, errors='replace')
raise ValueError(
"encoding %r must be one of %r" % (encoding, cls.ENCODINGS)
)
prefix = {
cls.ASCII: cls._ASCII_PREFIX,
cls.JIS: cls._JIS_PREFIX,
cls.UNICODE: cls._UNICODE_PREFIX,
}[encoding]
internal_encoding = {cls.UNICODE: cls._UNICODE, cls.JIS: cls._JIS}.get(
encoding, encoding
)
return prefix + data.encode(internal_encoding, errors="replace")

View file

@ -20,14 +20,12 @@ def set_gain(camera, gain, value):
if gain not in [MMAL_PARAMETER_ANALOG_GAIN, MMAL_PARAMETER_DIGITAL_GAIN]:
raise ValueError("The gain parameter was not valid")
ret = mmal.mmal_port_parameter_set_rational(
camera._camera.control._port,
gain,
to_rational(value)
camera._camera.control._port, gain, to_rational(value)
)
if ret == 4:
raise exc.PiCameraMMALError(
ret,
"Are you running the latest version of the userland libraries? Gain setting was introduced in late 2017."
"Are you running the latest version of the userland libraries? Gain setting was introduced in late 2017.",
)
elif ret != 0:
raise exc.PiCameraMMALError(ret)
@ -56,21 +54,27 @@ if __name__ == "__main__":
# fix the shutter speed
cam.shutter_speed = cam.exposure_speed
logging.info("Current a/d gains: {}, {}".format(cam.analog_gain, cam.digital_gain))
logging.info(
"Current a/d gains: {}, {}".format(cam.analog_gain, cam.digital_gain)
)
logging.info("Attempting to set analogue gain to 1")
set_analog_gain(cam, 1)
logging.info("Attempting to set digital gain to 1")
set_digital_gain(cam, 1)
# The old code is left in here in case it is a useful example...
# ret = mmal.mmal_port_parameter_set_rational(cam._camera.control._port,
# ret = mmal.mmal_port_parameter_set_rational(cam._camera.control._port,
# MMAL_PARAMETER_DIGITAL_GAIN,
# to_rational(1))
# print("Return code: {}".format(ret))
try:
while True:
logging.info("Current a/d gains: {}, {}".format(cam.analog_gain, cam.digital_gain))
logging.info(
"Current a/d gains: {}, {}".format(
cam.analog_gain, cam.digital_gain
)
)
time.sleep(1)
except KeyboardInterrupt:
logging.info("Stopping...")