16 Commits
v0.0.1 ... v0.2

Author SHA1 Message Date
fa701c1ae7 exv.py : Utilisation de piexif (ajouté à "requirements.txt"). Merci à Christophe pour l'idée 2019-05-23 22:43:01 +02:00
cfc322a0fc dtlib.py : ne crash pas si une image n'a pas de donnée exif 2019-05-23 17:19:25 +02:00
0a4a75647a Optimisation lors du traitement des tags : gain x10 en temps d'exécution 2019-05-19 23:31:38 +02:00
2dce5901e3 Erreur de syntax MarkDown 2019-05-19 22:01:10 +02:00
2684926e15 Version plus claire du README 2019-05-19 21:56:19 +02:00
17ce459630 Now reads only specified files if extension is provided. 2019-05-19 20:09:18 +02:00
ed525ccf2f bugfix : try to use different exif tag for datetime 2019-05-17 19:01:46 +02:00
6e702b3b3b Analyze only exif enabled files 2019-05-17 18:42:23 +02:00
664c8957fe Recursive if directory 2019-05-17 18:32:58 +02:00
f558b6574a Modification de la FR pour LaTeX 2019-05-17 16:46:18 +02:00
62d759ab41 Feature request : LaTeX 2019-05-17 15:51:44 +02:00
3fca8eb458 Ajout de "Comment contribuer ?" 2019-05-17 15:26:45 +02:00
6962085961 Ajout des features requests 2019-05-17 14:59:58 +02:00
1ea48e5af5 Suppression d'une ligne inutile 2019-05-17 12:57:48 +02:00
e91d51a43f README en MarkDown 2019-05-17 12:57:08 +02:00
47a2baf847 Updates 2019-05-17 12:32:15 +02:00
6 changed files with 355 additions and 96 deletions

17
README
View File

@ -1,17 +0,0 @@
===========
PhotoReport
===========
Dependencies
============
- Python 3 (and some additionnals modules, see 'requirements.txt')
- rst2pdf
- ImageMagick
Howto install ?
===============
- create a virtualenv with python3
- install python requirements
- uwsgi and stuff.... (TODO)

47
README.md Normal file
View File

@ -0,0 +1,47 @@
# PhotoReport
## Dépendences
- Python 3
- exiv2
## Installation
- Créer un environnement virtuel python3 (e.g. : `virtulenv3 PhotoReport && source bin/activate`)
- Cloner le dépôts (`git clone https://git.antoineve.me/PhotoReport/`)
- Installer les dépendances (Note : pour l'instant, version 0.1, ce n'est pas nécessaire)
## Utilisation en ligne de commande
### exv.py
L'aide est disponible avec `./exv.py --help`.
Exemple d'utilisation :
./exv.py -R -x jpg ~/Nextcloud/Photos/2019/
Exemple de sortie : [sortie_exemple.json](https://git.antoineve.me/PhotoReport/tree/sortie_exemple.json)
## Comment contribuer ?
- Cloner le dépôt : `git clone https://git.antoineve.me/PhotoReport/`
- Créer une branche : `git checkout -b <new_feature>`
- Coder et commiter : `git commit -a -m <description>`
- Créer le patch : `git format-patch $(git merge-base --fork-point master)..<new_feature>`
- M'envoyer le patch par mail : [antoine+photoreport@van-elstraete.net](mailto:antoine+photoreport@van-elstraete.net)
- J'applique le patch avec `git am --signoff -k < <new_feature>.patch`
## BUGS & TODO
- Pour le moment, pas de sortie en PDF
- La base de donnée de darktable a changé depuis la première écriture du script
- La vitesse d'obturation n'est pas arrondie, est-ce souhaitable ? (16.666 ≠ 16.666666666666668 ?)
## Features requests et développement prévu
- Créer une option pour filtrer par objectif **-> branche filters/lens**
- Créer une option pour filtrer par boitier **-> branche filters/camera**
- Sortie PDF fonctionnelle **-> branche output/pdf_via_md**
- Créer une option pour une sortie exploitable par LaTeX

View File

@ -6,28 +6,32 @@ from datetime import datetime
def extractor(dt_file, start, end):
catalog = {}
unknow_files = 0
conn = sqlite3.connect(dt_file)
cursor = conn.cursor()
req = "SELECT id, model, lens, exposure, aperture, iso, focal_length, datetime_taken, width, height FROM images;"
cursor.execute(req)
res = cursor.fetchall()
for data in res:
img_date = datetime.strptime(data[7], "%Y:%m:%d %H:%M:%S")
if start <= img_date <= end:
catalog.update({
data[0]:
{
'camera': data[1],
'lens': data[2],
'shutter': float(data[3]*1000),
'aperture': round(float(data[4]), 1),
'iso': int(data[5]),
'focal': float(data[6]),
'datetime': img_date,
'height': int(data[9]),
'width': int(data[8])
}
})
if not data[7]:
unknow_files += 1
else:
img_date = datetime.strptime(data[7], "%Y:%m:%d %H:%M:%S")
if start <= img_date <= end:
catalog.update({
data[0]:
{
'camera': data[1],
'lens': data[2],
'shutter': float(data[3]*1000),
'aperture': round(float(data[4]), 1),
'iso': int(data[5]),
'focal': float(data[6]),
'datetime': img_date,
'height': int(data[9]),
'width': int(data[8])
}
})
cameras, lenses, focals, apertures, shutter_speeds = {}, {}, {}, {}, {}
isos, dimensions, cameras_lenses, dates = {}, {}, {}, {}
cameras_list, lenses_list, focals_list, apertures_list, shutter_speeds_list = [], [], [], [], []
@ -76,6 +80,7 @@ def extractor(dt_file, start, end):
dates.update({date: dates_list.count(date)})
return {
"total": len(catalog.keys()),
"unknows": unknow_files,
"date": dates,
"cameras": cameras,
"lenses": lenses,

162
exv.py
View File

@ -1,83 +1,110 @@
#!/usr/bin/env python3
from subprocess import Popen, PIPE
import piexif
from datetime import datetime
import json
from os.path import isfile
from os.path import isfile, isdir
from os import walk
TYPES = [
"JPEG",
"JPG",
"CR2",
"TIFF",
"TIF",
"DNG",
"NEF",
]
def extractor(input_files, start, end):
def extractor(input_files, start, end, recursive, extensions):
if extensions:
extensions = [ext.upper() for ext in extensions]
if not isinstance(input_files, type(list())):
raise ValueError("Input files must be a list.")
exif_dict_list = []
usefull_exif = [
"Image.Model",
"Photo.LensModel",
"Photo.FocalLength",
"Photo.ApertureValue",
"Photo.ExposureTime",
"Photo.ISOSpeedRatings",
"Photo.PixelXDimension",
"Photo.PixelYDimension",
"Image.ImageWidth",
"Image.ImageLength",
"Photo.DateTimeOriginal"
]
files = []
usefull_exif = {
"Model": "",
"LensModel": "",
"FocalLength": 0,
"ApertureValue": 0,
"ExposureTime": 0,
"ISOSpeedRatings": 0,
"PixelXDimension": 0,
"PixelYDimension": 0,
"ImageWidth": 0,
"ImageLength": 0,
"Pixels": None,
"Dimension": 0,
"DateTime": None,
}
for item in input_files:
if isdir(item):
for (dirpath, dirnames, filenames) in walk(item):
for filename in filenames:
if filename.split(".")[-1].upper() in TYPES:
files.append("{}/{}".format(dirpath, filename))
if not recursive:
break
else:
files.append(item)
if extensions:
files_with_ext_filter = []
for filename in files:
if filename.split(".")[-1].upper() in extensions:
files_with_ext_filter.append("{}".format(filename))
input_files = files_with_ext_filter
else:
input_files = files
for input_file in input_files:
exif_dict = {}
exif_dict = usefull_exif
if not isfile(input_file):
raise ValueError("{} doesn't exist here.".format(input_file))
exif_dict.update({'file': input_file})
for extracted in usefull_exif:
cmd = ["exiv2", "-g", extracted, "-Pt", input_file]
with Popen(cmd, stdout=PIPE) as extracted_data:
extracted_data = extracted_data.stdout.read().decode()
if extracted_data:
extracted_data = extracted_data.splitlines()[0]
exif_dict.update({extracted.split(".")[1]: extracted_data})
else:
exif_dict.update({extracted.split(".")[1]: None})
if exif_dict['FocalLength']:
exif_dict['FocalLength'] = float(
exif_dict['FocalLength'].replace(" mm", ""))
if exif_dict['ApertureValue']:
exif_dict['ApertureValue'] = float(
exif_dict['ApertureValue'].replace("F", ""))
if exif_dict['PixelXDimension'] and exif_dict['PixelYDimension']:
exif_dict['PixelXDimension'] = int(exif_dict['PixelXDimension'])
exif_dict['PixelYDimension'] = int(exif_dict['PixelYDimension'])
pixels = exif_dict['PixelXDimension'] * \
exif_dict['PixelYDimension']
elif exif_dict['ImageLength'] and exif_dict['ImageWidth']:
exif_dict['PixelXDimension'] = int(exif_dict['ImageLength'])
exif_dict['PixelYDimension'] = int(exif_dict['ImageWidth'])
pixels = exif_dict['PixelXDimension'] * \
exif_dict['PixelYDimension']
else:
pixels = None
if exif_dict['DateTimeOriginal']:
exif_dict['DateTimeOriginal'] = datetime.strptime(
exif_dict['DateTimeOriginal'], "%Y:%m:%d %H:%M:%S")
if exif_dict['ExposureTime']:
try:
exif_dict['ExposureTime'] = float(
exif_dict['ExposureTime'].split(" ")[0])
except ValueError:
exposure = exif_dict['ExposureTime'].split(" ")[0]
exif_dict['ExposureTime'] = float(
1/(float(exposure.split("/")[1])))
if pixels:
exif_dict.update({'Dimension': round((pixels/10**6), 1)})
else:
exif_dict.update({'Dimension': None})
exif_tags = piexif.load(input_file)
if 0x0110 in exif_tags['0th']:
exif_dict.update({'Model':
exif_tags['0th'][0x0110].decode()})
if 0xa434 in exif_tags['Exif']:
exif_dict.update({'LensModel':
exif_tags['Exif'][0xa434].decode().split("\x00")[0]})
if 0x920a in exif_tags['Exif']:
exif_dict.update({'FocalLength':
float(exif_tags['Exif'][0x920a][0] / exif_tags['Exif'][0x920a][1])})
if 0x829d in exif_tags['Exif']:
exif_dict.update({'ApertureValue':
float(exif_tags['Exif'][0x829d][0]/exif_tags['Exif'][0x829d][1])})
if 0x829a in exif_tags['Exif']:
exif_dict.update({'ExposureTime':
float(exif_tags['Exif'][0x829a][0]/exif_tags['Exif'][0x829a][1])})
if 0x8827 in exif_tags['Exif']:
exif_dict.update({'ISOSpeedRatings':
int(exif_tags['Exif'][0x8827])})
if 0xa002 in exif_tags['Exif'] and 0xa003 in exif_tags['Exif']:
exif_dict.update({'Pixels':
int(exif_tags['Exif'][0xa002]) * int(exif_tags['Exif'][0xa003])})
elif 0x0100 in exif_tags['0th'] and 0x0101 in exif_tags['0th']:
exif_dict.update({'Pixels':
int(exif_tags['0th'][0x0100]) * int(exif_tags['0th'][0x0101])})
if 0x9003 in exif_tags['Exif']:
exif_dict['DateTime'] = datetime.strptime(
exif_tags['Exif'][0x9003].decode(), "%Y:%m:%d %H:%M:%S")
elif 0x9004 in exif_tags['Exif']:
exif_dict['DateTime'] = datetime.strptime(
exif_tags['Exif'][0x9004].decode(), "%Y:%m:%d %H:%M:%S")
elif 0x0132 in exif_tags['0th']:
exif_dict['DateTime'] = datetime.strptime(
exif_tags['0th'][0x0132].decode(), "%Y:%m:%d %H:%M:%S")
if exif_dict['Pixels']:
exif_dict.update({'Dimension': round((exif_dict['Pixels']/10**6), 1)})
exif_dict_list.append(exif_dict)
cameras, lenses, focals, apertures, exposures = {}, {}, {}, {}, {}
isos, dimensions, cameras_lenses, dates = {}, {}, {}, {}
cameras_list, lenses_list, focals_list, apertures_list, exposures_list = [], [], [], [], []
isos_list, dimensions_list, cameras_lenses_list, dates_list = [], [], [], []
for data in exif_dict_list:
if start <= data['DateTimeOriginal'] <= end:
if data['DateTime'] and start <= data['DateTime'] <= end:
cameras_list.append(data['Model'])
lenses_list.append(data['LensModel'])
focals_list.append(data['FocalLength'])
@ -88,7 +115,7 @@ def extractor(input_files, start, end):
if data['LensModel']:
cameras_lenses_list.append(
"{} + {}".format(data['Model'], data['LensModel']))
dates_list.append(data['DateTimeOriginal'].strftime("%Y%m%d"))
dates_list.append(data['DateTime'].strftime("%Y%m%d"))
for camera in list(set(cameras_list)):
if camera:
cameras.update({camera: cameras_list.count(camera)})
@ -139,7 +166,15 @@ if __name__ == '__main__':
"infile", help="input file (EXV, JPG, CR2, ...). Can be more than one.", nargs='+')
parser.add_argument("-s", "--start-date", help="start date as YYYYMMDD. If omitted, begin of the collection.", const=None)
parser.add_argument("-e", "--end-date", help="end date as YYYYMMDD. If omitted, today", const=None)
parser.add_argument("-R", "--recursive", help="Walk throught directory to find all files", action='store_true')
parser.add_argument("-x", "--extension", help="Read files with this extension only (can be specified more than one time)", action='append')
args = parser.parse_args()
if not args.recursive:
recursive = False
if not args.extension:
extensions = None
else:
extensions = args.extension
if not args.start_date:
args.start_date = "18250101" # Ok, that's the year of the 1st photography by Nicéphore Niépce. Shoul'd be enough for a start date.
if not args.end_date:
@ -150,4 +185,5 @@ if __name__ == '__main__':
except ValueError:
print("Date must be YYYYMMDD.")
exit()
print(json.dumps(extractor(args.infile, datetime.strptime(args.start_date, "%Y%m%d"), datetime.strptime(args.end_date, "%Y%m%d")), indent=4, sort_keys=True))
recursive = args.recursive
print(json.dumps(extractor(args.infile, datetime.strptime(args.start_date, "%Y%m%d"), datetime.strptime(args.end_date, "%Y%m%d"), recursive, extensions), indent=2, sort_keys=True))

View File

@ -2,3 +2,4 @@ Flask
numpy
matplotlib
babel
piexif

187
sortie_exemple.json Normal file
View File

@ -0,0 +1,187 @@
{
"apertures": {
"2.0": 2,
"2.2": 113,
"2.8": 2,
"3.5": 1,
"4.0": 12,
"5.7": 2,
"7.0": 7,
"8.0": 15,
"9.1": 6,
"9.9": 2,
"11.0": 1
},
"cameras": {
"ANE-LX1": 6,
"Canon EOS 1100D": 62,
"FIG-LX1": 117
},
"cameras+lenses": {
"Canon EOS 1100D + 70-300mm": 20,
"Canon EOS 1100D + EF-S24mm f/2.8 STM": 28
},
"date": {
"20190101": 1,
"20190103": 2,
"20190104": 10,
"20190105": 4,
"20190107": 1,
"20190110": 6,
"20190112": 4,
"20190115": 1,
"20190116": 3,
"20190119": 1,
"20190120": 3,
"20190122": 1,
"20190125": 1,
"20190130": 12,
"20190202": 5,
"20190203": 2,
"20190205": 17,
"20190206": 2,
"20190209": 2,
"20190210": 13,
"20190214": 1,
"20190215": 1,
"20190216": 7,
"20190217": 1,
"20190218": 6,
"20190220": 1,
"20190222": 1,
"20190223": 3,
"20190227": 1,
"20190314": 1,
"20190316": 7,
"20190407": 16,
"20190417": 9,
"20190423": 6,
"20190424": 9,
"20190426": 24
},
"dimensions": {
"4.3": 1,
"5.1": 1,
"5.3": 1,
"5.4": 1,
"6.9": 1,
"7.7": 1,
"8.0": 2,
"8.1": 1,
"8.2": 1,
"9.7": 2,
"10.2": 1,
"10.7": 13,
"11.1": 1,
"11.5": 1,
"12.2": 43,
"13.0": 102,
"15.5": 1,
"15.9": 6,
"19.3": 1,
"19.7": 1,
"26.9": 1,
"33.5": 1,
"35.4": 1
},
"focals": {
"2.8": 1,
"3.2": 108,
"3.6": 1,
"3.8": 5,
"24.0": 42,
"70.0": 10,
"108.0": 1,
"133.0": 1,
"149.0": 2,
"168.0": 2,
"214.0": 2,
"300.0": 2
},
"isos": {
"100": 48,
"1000": 3,
"125": 4,
"1250": 2,
"160": 4,
"200": 1,
"2500": 3,
"320": 6,
"400": 22,
"50": 39,
"500": 5,
"64": 11,
"640": 4,
"80": 14,
"800": 11
},
"lenses": {
"70-300mm": 20,
"EF-S24mm f/2.8 STM": 28
},
"shutter_speeds": {
"0.25": 3,
"0.263": 1,
"0.499": 1,
"0.5": 1,
"0.7380000000000001": 1,
"0.7929999999999999": 1,
"0.877": 1,
"0.92": 1,
"1.0": 2,
"1.0759999999999998": 1,
"1.2329999999999999": 1,
"1.25": 4,
"1.253": 1,
"1.2730000000000001": 1,
"1.306": 1,
"1.414": 1,
"1.464": 1,
"1.4649999999999999": 1,
"1.5625": 6,
"1.595": 1,
"1.601": 1,
"1.727": 1,
"1.803": 1,
"1.971": 1,
"2.0": 4,
"2.0300000000000002": 1,
"2.161": 1,
"2.29": 1,
"2.5": 4,
"3.125": 6,
"3.218": 1,
"4.0": 6,
"4.674": 1,
"5.0": 5,
"5.129": 1,
"5.191999999999999": 1,
"6.25": 1,
"6.6290000000000004": 2,
"7.103": 1,
"8.0": 2,
"8.226": 1,
"9.058": 1,
"9.27": 1,
"9.802": 1,
"9.993": 1,
"10.0": 13,
"12.5": 2,
"16.666": 1,
"16.666666666666668": 12,
"16.667": 1,
"20.0": 17,
"25.0": 2,
"30.0": 20,
"33.333": 2,
"33.333333333333336": 1,
"40.0": 5,
"41.667": 2,
"50.0": 3,
"58.333000000000006": 1,
"60.0": 7,
"70.0": 7,
"80.0": 3
},
"total": 185
}