def predict(self, image_file=None, location=None): if location: lon, lat = location else: metadata = PhotoMetadata(image_file) location = metadata.get('GPS Position') and parse_gps_location( metadata.get('GPS Position')) or None if location: lon, lat = location else: return { 'country': None, 'city': None, } country = self.get_country(lon=lon, lat=lat) if country: city = self.get_city(lon=lon, lat=lat, country_code=country['code']) else: city = self.get_city(lon=lon, lat=lat) if not country and city: country = { 'name': city['country_name'], } return { 'country': country, 'city': city, }
def resolve_photo_file_metadata(self, info, **kwargs): """Return metadata for photofile.""" photo_file = PhotoFile.objects.filter(id=kwargs.get('photo_file_id')) if photo_file and os.path.exists(photo_file[0].path): metadata = PhotoMetadata(photo_file[0].path) return {'data': metadata.get_all(), 'ok': True} return {'ok': False}
def get_thumbnail(photo, width=256, height=256, crop='cover', quality=75, return_type='path', force_regenerate=False): if not isinstance(photo, Photo): photo = Photo.objects.get(id=photo) # If thumbnail image was previously generated and we weren't told to re-generate, return that one output_path = get_thumbnail_path(photo, width, height, crop, quality) if os.path.exists(output_path): if return_type == 'bytes': return open(output_path, 'rb').read() else: return output_path # Read base image and metadata input_path = photo.base_image_path im = Image.open(input_path) if im.mode != 'RGB': im = im.convert('RGB') metadata = PhotoMetadata(input_path) # Perform rotations if decalared in metadata if metadata.get('Orientation') in ['Rotate 90 CW', 'Rotate 270 CCW']: im = im.rotate(-90, expand=True) elif metadata.get('Orientation') in ['Rotate 90 CCW', 'Rotate 270 CW']: im = im.rotate(90, expand=True) # Crop / resize if crop == 'cover': im = ImageOps.fit(im, (width, height), Image.ANTIALIAS) else: im.thumbnail((width, height), Image.ANTIALIAS) # Save to disk (keeping the bytes in memory if we need to return them) if return_type == 'bytes': img_byte_array = io.BytesIO() im.save(img_byte_array, format='JPEG', quality=quality) with open(output_path, 'wb') as f: f.write(img_byte_array.getvalue()) else: im.save(output_path, format='JPEG', quality=quality) # Update Photo DB model photo.last_thumbnailed_version = 0 photo.last_thumbnailed_at = timezone.now() photo.save() # Return accordingly if return_type == 'bytes': return img_byte_array.getvalue() return output_path
def test_metadata(): # General EXIF metadata photo_path = str(Path(__file__).parent / 'photos' / 'snow.jpg') metadata = PhotoMetadata(photo_path) assert metadata.get('Image Size') == '800x600' assert metadata.get('Date Time') == '2018:02:28 07:16:25' assert metadata.get('Make') == 'Xiaomi' assert metadata.get('ISO') == '100' # Ignore invalid UTF-8 that might be in the metadata photo_path = str(Path(__file__).parent / 'photos' / 'invalid_utf8.jpg') metadata = PhotoMetadata(photo_path) assert len(metadata.get_all().keys()) > 30 assert metadata.get('Artist') == ''
def predict(self, image_file, min_score=0.99): # Detects face bounding boxes image = Image.open(image_file) if image.mode != 'RGB': image = image.convert('RGB') # Perform rotations if decalared in metadata metadata = PhotoMetadata(image_file) if metadata.get('Orientation') in ['Rotate 90 CW', 'Rotate 270 CCW']: image = image.rotate(-90, expand=True) elif metadata.get('Orientation') in ['Rotate 90 CCW', 'Rotate 270 CW']: image = image.rotate(90, expand=True) image = np.asarray(image) results = self.graph['mtcnn'].detect_faces(image) return list(filter(lambda f: f['confidence'] > min_score, results))
def predict(self, image_file, min_score=0.1): image = Image.open(image_file) if image.mode != 'RGB': image = image.convert('RGB') # Perform rotations if decalared in metadata metadata = PhotoMetadata(image_file) if metadata.get('Orientation') in ['Rotate 90 CW', 'Rotate 270 CCW']: image = image.rotate(-90, expand=True) elif metadata.get('Orientation') in ['Rotate 90 CCW', 'Rotate 270 CW']: image = image.rotate(90, expand=True) # the array based representation of the image will be used later in order to prepare the # result image with boxes and labels on it. image_np = self.load_image_into_numpy_array(image) # Expand dimensions since the model expects images to have shape: [1, None, None, 3] np.expand_dims(image_np, axis=0) # Actual detection. output_dict = self.run_inference_for_single_image(image_np) return self.format_output(output_dict, min_score)
def predict(self, image_file): metadata = PhotoMetadata(image_file) date_taken = None possible_date_keys = ['Date/Time Original', 'Date Time Original', 'Date/Time', 'Date Time', 'GPS Date/Time', 'Modify Date', 'File Modification Date/Time'] for date_key in possible_date_keys: date_taken = parse_datetime(metadata.get(date_key)) if date_taken: events = { datetime.date(date_taken.year, 12, 25): "Christmas Day", datetime.date(date_taken.year, 10, 31): "Halloween", datetime.date(date_taken.year, 2, 14): "Valentine's Day", datetime.date(date_taken.year, 12, 31): "New Year Start", datetime.date(date_taken.year, 1, 1): "New Year End", } if events.get(date_taken.date()): if events.get(date_taken.date()).startswith("New Year"): start_of_day = datetime.datetime.combine(datetime.date(date_taken.year, 12, 31), datetime.datetime.min.time()) end_of_day = start_of_day + datetime.timedelta(days=1) if start_of_day <= date_taken.replace(tzinfo=None) <= end_of_day: return ['New Year'] return [events.get(date_taken.date())] return []
def test_metadata(): # General exif metadata photo_path = str(Path(__file__).parent / 'photos' / 'snow.jpg') metadata = PhotoMetadata(photo_path) assert metadata.get('Image Size') == '800x600' assert metadata.get('Date Time') == '2018:02:28 07:16:25' assert metadata.get('Make') == 'Xiaomi' assert metadata.get('ISO') == '100'
def record_photo(path): file_modified_at = datetime.fromtimestamp(os.stat(path).st_mtime, tz=utc) try: photo_file = PhotoFile.objects.get(path=path) except PhotoFile.DoesNotExist: photo_file = PhotoFile() if photo_file and photo_file.file_modified_at == file_modified_at: return False metadata = PhotoMetadata(path) date_taken = parse_datetime(metadata.get('Date/Time Original')) camera = None camera_make = metadata.get('Make') camera_model = metadata.get('Camera Model Name') if camera_model: camera_model = camera_model.replace(camera_make, '').strip() if camera_make and camera_model: try: camera = Camera.objects.get(make=camera_make, model=camera_model) if date_taken < camera.earliest_photo: camera.earliest_photo = date_taken camera.save() if date_taken > camera.latest_photo: camera.latest_photo = date_taken camera.save() except Camera.DoesNotExist: camera = Camera(make=camera_make, model=camera_model, earliest_photo=date_taken, latest_photo=date_taken) camera.save() lens = None lens_name = metadata.get('Lens ID') if lens_name: try: lens = Lens.objects.get(name=lens_name) if date_taken < lens.earliest_photo: lens.earliest_photo = date_taken lens.save() if date_taken > lens.latest_photo: lens.latest_photo = date_taken lens.save() except Lens.DoesNotExist: lens = Lens(name=lens_name, earliest_photo=date_taken, latest_photo=date_taken) lens.save() photo = None if date_taken: try: # TODO: Match on file number/file name as well photo = Photo.objects.get(taken_at=date_taken) except Photo.DoesNotExist: pass latitude = None longitude = None if metadata.get('GPS Position'): latitude, longitude = parse_gps_location(metadata.get('GPS Position')) if not photo: # Save Photo photo = Photo( taken_at=date_taken, taken_by=metadata.get('Artist') or None, aperture=metadata.get('Aperture') and Decimal(metadata.get('Aperture')) or None, exposure=metadata.get('Exposure Time') or None, iso_speed=metadata.get('ISO') and int(metadata.get('ISO')) or None, focal_length=metadata.get('Focal Length') and metadata.get('Focal Length').split(' ', 1)[0] or None, flash=metadata.get('Flash') and 'on' in metadata.get('Flash').lower() or False, metering_mode=metadata.get('Metering Mode') or None, drive_mode=metadata.get('Drive Mode') or None, shooting_mode=metadata.get('Shooting Mode') or None, camera=camera, lens=lens, latitude=latitude, longitude=longitude, altitude=metadata.get('GPS Altitude') and metadata.get('GPS Altitude').split(' ')[0]) photo.save() # Save PhotoFile photo_file.photo = photo photo_file.path = path photo_file.width = metadata.get('Image Width') photo_file.height = metadata.get('Image Height') photo_file.mimetype = mimetypes.guess_type(path)[0] photo_file.file_modified_at = file_modified_at photo_file.bytes = os.stat(path).st_size photo_file.preferred = False # TODO photo_file.save() # Create task to ensure JPEG version of file exists (used for thumbnailing, analysing etc.) Task(type='ensure_raw_processed', subject_id=photo.id, complete_with_children=True).save() return photo
def record_photo(path, library, inotify_event_type=None): logger.info(f'Recording photo {path}') mimetype = get_mimetype(path) if not imghdr.what( path) and not mimetype in MIMETYPE_WHITELIST and subprocess.run( ['dcraw', '-i', path]).returncode: logger.error(f'File is not a supported type: {path} ({mimetype})') return None if type(library) == Library: library_id = library.id else: library_id = str(library) try: photo_file = PhotoFile.objects.get(path=path) except PhotoFile.DoesNotExist: photo_file = PhotoFile() if inotify_event_type in ['DELETE', 'MOVED_FROM']: if PhotoFile.objects.filter(path=path).exists(): return delete_photo_record(photo_file) else: return True file_modified_at = datetime.fromtimestamp(os.stat(path).st_mtime, tz=utc) if photo_file and photo_file.file_modified_at == file_modified_at: return True metadata = PhotoMetadata(path) date_taken = None possible_date_keys = [ 'Create Date', 'Date/Time Original', 'Date Time Original', 'Date/Time', 'Date Time', 'GPS Date/Time', 'File Modification Date/Time' ] for date_key in possible_date_keys: date_taken = parse_datetime(metadata.get(date_key)) if date_taken: break # If EXIF data not found. date_taken = date_taken or datetime.strptime( time.ctime(os.path.getctime(path)), "%a %b %d %H:%M:%S %Y") camera = None camera_make = metadata.get('Make', '')[:Camera.make.field.max_length] camera_model = metadata.get('Camera Model Name', '') if camera_model: camera_model = camera_model.replace(camera_make, '').strip() camera_model = camera_model[:Camera.model.field.max_length] if camera_make and camera_model: try: camera = Camera.objects.get(library_id=library_id, make=camera_make, model=camera_model) if date_taken < camera.earliest_photo: camera.earliest_photo = date_taken camera.save() if date_taken > camera.latest_photo: camera.latest_photo = date_taken camera.save() except Camera.DoesNotExist: camera = Camera(library_id=library_id, make=camera_make, model=camera_model, earliest_photo=date_taken, latest_photo=date_taken) camera.save() lens = None lens_name = metadata.get('Lens ID') if lens_name: try: lens = Lens.objects.get(name=lens_name) if date_taken < lens.earliest_photo: lens.earliest_photo = date_taken lens.save() if date_taken > lens.latest_photo: lens.latest_photo = date_taken lens.save() except Lens.DoesNotExist: lens = Lens(library_id=library_id, name=lens_name, earliest_photo=date_taken, latest_photo=date_taken) lens.save() photo = None if date_taken: try: # TODO: Match on file number/file name as well photo = Photo.objects.get(taken_at=date_taken) except Photo.DoesNotExist: pass latitude = None longitude = None if metadata.get('GPS Position'): latitude, longitude = parse_gps_location(metadata.get('GPS Position')) iso_speed = None if metadata.get('ISO'): try: iso_speed = int(re.search(r'[0-9]+', metadata.get('ISO')).group(0)) except AttributeError: pass if not photo: # Save Photo aperture = None aperturestr = metadata.get('Aperture') if aperturestr: try: aperture = Decimal(aperturestr) if aperture.is_infinite(): aperture = None except: pass photo = Photo( library_id=library_id, taken_at=date_taken, taken_by=metadata.get( 'Artist', '')[:Photo.taken_by.field.max_length] or None, aperture=aperture, exposure=metadata.get( 'Exposure Time', '')[:Photo.exposure.field.max_length] or None, iso_speed=iso_speed, focal_length=metadata.get('Focal Length') and metadata.get('Focal Length').split(' ', 1)[0] or None, flash=metadata.get('Flash') and 'on' in metadata.get('Flash').lower() or False, metering_mode=metadata.get( 'Metering Mode', '')[:Photo.metering_mode.field.max_length] or None, drive_mode=metadata.get( 'Drive Mode', '')[:Photo.drive_mode.field.max_length] or None, shooting_mode=metadata.get( 'Shooting Mode', '')[:Photo.shooting_mode.field.max_length] or None, camera=camera, lens=lens, latitude=latitude, longitude=longitude, altitude=metadata.get('GPS Altitude') and metadata.get('GPS Altitude').split(' ')[0], star_rating=metadata.get('Rating')) photo.save() for subject in metadata.get('Subject', '').split(','): subject = subject.strip() if subject: tag, _ = Tag.objects.get_or_create(library_id=library_id, name=subject, type="G") PhotoTag.objects.create(photo=photo, tag=tag, confidence=1.0) else: for photo_file in photo.files.all(): if not os.path.exists(photo_file.path): photo_file.delete() width = metadata.get('Image Width') height = metadata.get('Image Height') if metadata.get('Orientation') in [ 'Rotate 90 CW', 'Rotate 270 CCW', 'Rotate 90 CCW', 'Rotate 270 CW' ]: old_width = width width = height height = old_width # Save PhotoFile photo_file.photo = photo photo_file.path = path photo_file.width = width photo_file.height = height photo_file.mimetype = mimetype photo_file.file_modified_at = file_modified_at photo_file.bytes = os.stat(path).st_size photo_file.preferred = False # TODO photo_file.save() # Create task to ensure JPEG version of file exists (used for thumbnailing, analysing etc.) Task(type='ensure_raw_processed', subject_id=photo.id, complete_with_children=True, library=photo.library).save() return photo
def record_photo(path, library, inotify_event_type=None): if type(library) == Library: library_id = library.id else: library_id = str(library) try: photo_file = PhotoFile.objects.get(path=path) except PhotoFile.DoesNotExist: photo_file = PhotoFile() if inotify_event_type in ['DELETE', 'MOVED_FROM']: if PhotoFile.objects.filter(path=path).exists(): return delete_photo_record(photo_file) else: return False file_modified_at = datetime.fromtimestamp(os.stat(path).st_mtime, tz=utc) if photo_file and photo_file.file_modified_at == file_modified_at: return False metadata = PhotoMetadata(path) date_taken = None possible_date_keys = [ 'Date/Time Original', 'Date Time Original', 'Date/Time', 'Date Time', 'GPS Date/Time', 'Modify Date', 'File Modification Date/Time' ] for date_key in possible_date_keys: date_taken = parse_datetime(metadata.get(date_key)) if date_taken: break camera = None camera_make = metadata.get('Make') if camera_make is None: camera_make = '' camera_model = metadata.get('Camera Model Name') if camera_model: camera_model = camera_model.replace(camera_make, '').strip() if camera_make and camera_model: try: camera = Camera.objects.get(library_id=library_id, make=camera_make, model=camera_model) if date_taken < camera.earliest_photo: camera.earliest_photo = date_taken camera.save() if date_taken > camera.latest_photo: camera.latest_photo = date_taken camera.save() except Camera.DoesNotExist: camera = Camera(library_id=library_id, make=camera_make, model=camera_model, earliest_photo=date_taken, latest_photo=date_taken) camera.save() lens = None lens_name = metadata.get('Lens ID') if lens_name: try: lens = Lens.objects.get(name=lens_name) if date_taken < lens.earliest_photo: lens.earliest_photo = date_taken lens.save() if date_taken > lens.latest_photo: lens.latest_photo = date_taken lens.save() except Lens.DoesNotExist: lens = Lens(library_id=library_id, name=lens_name, earliest_photo=date_taken, latest_photo=date_taken) lens.save() photo = None if date_taken: try: # TODO: Match on file number/file name as well photo = Photo.objects.get(taken_at=date_taken) except Photo.DoesNotExist: pass latitude = None longitude = None if metadata.get('GPS Position'): latitude, longitude = parse_gps_location(metadata.get('GPS Position')) iso_speed = None if metadata.get('ISO'): try: iso_speed = int(re.search(r'[0-9]+', metadata.get('ISO')).group(0)) except AttributeError: pass if not photo: # Save Photo aperture = None aperturestr = metadata.get('Aperture') if aperturestr: try: aperture = Decimal(aperturestr) if aperture.is_infinite(): aperture = None except: pass photo = Photo(library_id=library_id, taken_at=date_taken, taken_by=metadata.get('Artist') or None, aperture=aperture, exposure=metadata.get('Exposure Time') or None, iso_speed=iso_speed, focal_length=metadata.get('Focal Length') and metadata.get('Focal Length').split(' ', 1)[0] or None, flash=metadata.get('Flash') and 'on' in metadata.get('Flash').lower() or False, metering_mode=metadata.get('Metering Mode') or None, drive_mode=metadata.get('Drive Mode') or None, shooting_mode=metadata.get('Shooting Mode') or None, camera=camera, lens=lens, latitude=latitude, longitude=longitude, altitude=metadata.get('GPS Altitude') and metadata.get('GPS Altitude').split(' ')[0]) photo.save() width = metadata.get('Image Width') height = metadata.get('Image Height') if metadata.get('Orientation') in [ 'Rotate 90 CW', 'Rotate 270 CCW', 'Rotate 90 CCW', 'Rotate 270 CW' ]: old_width = width width = height height = old_width # Save PhotoFile photo_file.photo = photo photo_file.path = path photo_file.width = width photo_file.height = height photo_file.mimetype = mimetypes.guess_type(path)[0] photo_file.file_modified_at = file_modified_at photo_file.bytes = os.stat(path).st_size photo_file.preferred = False # TODO photo_file.save() # Create task to ensure JPEG version of file exists (used for thumbnailing, analysing etc.) Task(type='ensure_raw_processed', subject_id=photo.id, complete_with_children=True).save() return photo
def get_thumbnail(photo_file=None, photo=None, width=256, height=256, crop='cover', quality=75, return_type='path', force_regenerate=False, force_accurate=False): if not photo_file: if not isinstance(photo, Photo): photo = Photo.objects.get(id=photo) photo_file = photo.base_file elif not isinstance(photo_file, PhotoFile): photo_file = PhotoFile.objects.get(id=photo_file) # If thumbnail image was previously generated and we weren't told to re-generate, return that one output_path = get_thumbnail_path(photo_file.id, width, height, crop, quality) output_url = get_thumbnail_url(photo_file.id, width, height, crop, quality) if os.path.exists(output_path): if return_type == 'bytes': return open(output_path, 'rb').read() elif return_type == 'url': return output_url else: return output_path # Read base image and metadata input_path = photo_file.base_image_path ImageFile.LOAD_TRUNCATED_IMAGES = True im = Image.open(input_path) if im.mode != 'RGB': im = im.convert('RGB') metadata = PhotoMetadata(input_path) # Perform rotations if decalared in metadata if force_regenerate: im = im.rotate(photo_file.rotation, expand=True) elif metadata.get('Orientation') in ['Rotate 90 CW', 'Rotate 270 CCW']: im = im.rotate(-90, expand=True) elif metadata.get('Orientation') in ['Rotate 90 CCW', 'Rotate 270 CW']: im = im.rotate(90, expand=True) # Crop / resize if force_accurate: im = srgbResize(im, (width, height), crop, Image.BICUBIC) else: if crop == 'cover': im = ImageOps.fit(im, (width, height), Image.BICUBIC) else: im.thumbnail((width, height), Image.BICUBIC) # Save to disk (keeping the bytes in memory if we need to return them) if return_type == 'bytes': img_byte_array = io.BytesIO() im.save(img_byte_array, format='JPEG', quality=quality) with open(output_path, 'wb') as f: f.write(img_byte_array.getvalue()) else: im.save(output_path, format='JPEG', quality=quality) # Update PhotoFile DB model with version of thumbnailer if photo_file.thumbnailed_version != THUMBNAILER_VERSION: photo_file.thumbnailed_version = THUMBNAILER_VERSION photo_file.save() # Return accordingly if return_type == 'bytes': return img_byte_array.getvalue() elif return_type == 'url': return output_url return output_path