This commit is contained in:
parent
edeb2fa457
commit
94cc57fe7c
11 changed files with 844 additions and 33 deletions
|
@ -1,3 +1,9 @@
|
|||
# pylint: disable=C0103
|
||||
|
||||
"""
|
||||
exif-database: Dump pictures metadata into a MongoDB database for statistics purposes
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
|
@ -7,9 +13,9 @@ from pathlib import Path
|
|||
from platformdirs import user_data_dir
|
||||
from pymongo import MongoClient
|
||||
|
||||
from exif_json import execute_exiftool
|
||||
from exif_database.exiftool import execute_exiftool
|
||||
|
||||
_allowed_extensions = [
|
||||
_ALLOWED_EXTENSIONS = [
|
||||
'.ARW',
|
||||
'.NEF',
|
||||
]
|
||||
|
@ -19,7 +25,7 @@ def _load_pictures_cache() -> dict:
|
|||
_file_path = _get_make_pictures_cache_path()
|
||||
|
||||
try:
|
||||
with open(_file_path, 'r') as f:
|
||||
with open(_file_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except FileNotFoundError:
|
||||
return {}
|
||||
|
@ -28,7 +34,7 @@ def _load_pictures_cache() -> dict:
|
|||
def _save_pictures_cache(_pictures: dict):
|
||||
_file_path = _get_make_pictures_cache_path()
|
||||
|
||||
with open(_file_path, 'w') as f:
|
||||
with open(_file_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(_pictures, f)
|
||||
|
||||
|
||||
|
@ -40,7 +46,7 @@ def _get_make_pictures_cache_path():
|
|||
|
||||
|
||||
def _is_extension_allowed(_filename: str) -> bool:
|
||||
for _allowed_extension in _allowed_extensions:
|
||||
for _allowed_extension in _ALLOWED_EXTENSIONS:
|
||||
if _filename.endswith(_allowed_extension):
|
||||
return True
|
||||
|
||||
|
|
125
exif_database/exiftool.py
Normal file
125
exif_database/exiftool.py
Normal file
|
@ -0,0 +1,125 @@
|
|||
"""
|
||||
exif-database.exiftool: Python wrapper around exiftool(1)
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
|
||||
_FILE_DATE_FORMAT = "%Y:%m:%d %H:%M:%S%z"
|
||||
_ORIGINAL_DATE_FORMAT = "%Y:%m:%d %H:%M:%S.%f%z"
|
||||
_ORIGINAL_DATE_FORMAT_FALLBACK = "%Y:%m:%d %H:%M:%S.%f"
|
||||
|
||||
_DATE_FIELDS = {
|
||||
'file_modification_date/time': [_FILE_DATE_FORMAT],
|
||||
'file_access_date/time': [_FILE_DATE_FORMAT],
|
||||
'file_inode_change_date/time': [_FILE_DATE_FORMAT],
|
||||
'date/time_original': [_ORIGINAL_DATE_FORMAT, _ORIGINAL_DATE_FORMAT_FALLBACK],
|
||||
'create_date': [_ORIGINAL_DATE_FORMAT, _ORIGINAL_DATE_FORMAT_FALLBACK],
|
||||
'modify_date': [_ORIGINAL_DATE_FORMAT, _ORIGINAL_DATE_FORMAT_FALLBACK],
|
||||
}
|
||||
|
||||
_INTEGER_FIELDS = [
|
||||
'image_width',
|
||||
'image_height',
|
||||
'iso',
|
||||
'shutter_count',
|
||||
'jpg_from_raw_start',
|
||||
'jpg_from_raw_length',
|
||||
'thumbnail_offset',
|
||||
'thumbnail_length',
|
||||
'sr2_sub_ifd_offset',
|
||||
'sr2_sub_ifd_length',
|
||||
'exif_image_width',
|
||||
'exif_image_height',
|
||||
'shutter_count_2',
|
||||
'sony_iso',
|
||||
'iso_auto_min',
|
||||
'iso_auto_max',
|
||||
'bits_per_sample',
|
||||
'strip_byte_counts',
|
||||
'rows_per_strip',
|
||||
'strip_offsets',
|
||||
'x_resolution',
|
||||
'y_resolution',
|
||||
'samples_per_pixel',
|
||||
'sequence_file_number',
|
||||
'digital_zoom_ratio',
|
||||
'sequence_image_number',
|
||||
'focus_position_2',
|
||||
]
|
||||
|
||||
_DECIMAL_FIELDS = [
|
||||
'aperture',
|
||||
'megapixels',
|
||||
'light_value',
|
||||
'blue_balance',
|
||||
'sony_f_number',
|
||||
'sony_max_aperture_value',
|
||||
'sony_f_number_2',
|
||||
'f_number',
|
||||
'max_aperture_value',
|
||||
'brightness_value',
|
||||
'stops_above_base_iso',
|
||||
]
|
||||
|
||||
|
||||
def _parse_datetime(raw_value: str, available_formats: List[str]) -> datetime:
|
||||
for available_format in available_formats:
|
||||
try:
|
||||
return datetime.strptime(raw_value, available_format)
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
raise ValueError(f"Could not parse datetime '{raw_value}' ({available_formats})")
|
||||
|
||||
|
||||
def execute_exiftool(img_file: str) -> dict:
|
||||
"""
|
||||
Execute exiftool against given image and return results as a dictionary.
|
||||
:param img_file: path to image file
|
||||
:return: dictionary of exif attributes
|
||||
"""
|
||||
res = subprocess.run(
|
||||
['exiftool', img_file],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
|
||||
exif_metadata = {}
|
||||
|
||||
for line in res.stdout.splitlines():
|
||||
parts = line.split(':', 1)
|
||||
exif_metadata[parts[0].strip().lower().replace(' ', '_')] = parts[1].strip()
|
||||
|
||||
for (field, date_formats) in _DATE_FIELDS.items():
|
||||
if field in exif_metadata:
|
||||
try:
|
||||
exif_metadata[field] = _parse_datetime(exif_metadata[field], date_formats)
|
||||
except ValueError as e:
|
||||
print(f'Failed to convert {field} ({exif_metadata[field]}) to a datetime.')
|
||||
raise e
|
||||
|
||||
for field in _INTEGER_FIELDS:
|
||||
if field in exif_metadata:
|
||||
try:
|
||||
exif_metadata[field] = int(exif_metadata[field])
|
||||
except ValueError as e:
|
||||
print(f'Failed to convert {field} ({exif_metadata[field]}) to an integer.')
|
||||
raise e
|
||||
|
||||
for field in _DECIMAL_FIELDS:
|
||||
if field in exif_metadata:
|
||||
try:
|
||||
exif_metadata[field] = float(exif_metadata[field])
|
||||
except ValueError as e:
|
||||
print(f'Failed to convert {field} ({exif_metadata[field]}) to a float.')
|
||||
raise e
|
||||
|
||||
return exif_metadata
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(execute_exiftool(sys.argv[1]))
|
Loading…
Add table
Add a link
Reference in a new issue