Blackened everything
This commit is contained in:
parent
e213647217
commit
5966ce29be
57 changed files with 1938 additions and 1414 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -7,5 +7,4 @@ from ._exif import *
|
|||
from ._exceptions import *
|
||||
|
||||
|
||||
|
||||
VERSION = '1.1.2'
|
||||
VERSION = "1.1.2"
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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...")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue