5 Commits
v0.1 ... v0.2

5 changed files with 286 additions and 114 deletions

View File

@ -1,20 +1,28 @@
# PhotoReport # PhotoReport
## Dependencies ## Dépendences
- Python 3 (and some additionnals modules, see 'requirements.txt') - Python 3
- rst2pdf - exiv2
- ImageMagick
- exiv2 (for direct exif reading)
## Howto install ? ## Installation
- create a virtualenv with python3 - Créer un environnement virtuel python3 (e.g. : `virtulenv3 PhotoReport && source bin/activate`)
- install python requirements - 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)
## CLI usage ## 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)
[todo]
## Comment contribuer ? ## Comment contribuer ?
@ -25,14 +33,15 @@
- M'envoyer le patch par mail : [antoine+photoreport@van-elstraete.net](mailto:antoine+photoreport@van-elstraete.net) - 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` - J'applique le patch avec `git am --signoff -k < <new_feature>.patch`
## BUGS ## BUGS & TODO
For the moment, PDF report isn't working. - 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 ## Features requests et développement prévu
- Est-ce quil sera possible de filtrer par objectif/boîtier, parce quau final, les stats de focale/ouvertures sont surtout pertinentes en isolant un seul objectif (dans le cas des zooms) ? - Créer une option pour filtrer par objectif **-> branche filters/lens**
- En effet, loption -d serait utile, voire directement automatiser la recherche récursive - Créer une option pour filtrer par boitier **-> branche filters/camera**
- dédupliquer si le nom est le même avec une extension différente - Sortie PDF fonctionnelle **-> branche output/pdf_via_md**
- exv.py -d ./2019 -u -e NEF -e CR2 -e DNG -e JPG (Pour le dossier 2019, éviter les doubles, uniquement les extensions NEF, CR2, DNG, JPG, par exemple.) - Créer une option pour une sortie exploitable par LaTeX
- Sortie exploitable par LaTeX

View File

@ -6,12 +6,16 @@ from datetime import datetime
def extractor(dt_file, start, end): def extractor(dt_file, start, end):
catalog = {} catalog = {}
unknow_files = 0
conn = sqlite3.connect(dt_file) conn = sqlite3.connect(dt_file)
cursor = conn.cursor() cursor = conn.cursor()
req = "SELECT id, model, lens, exposure, aperture, iso, focal_length, datetime_taken, width, height FROM images;" req = "SELECT id, model, lens, exposure, aperture, iso, focal_length, datetime_taken, width, height FROM images;"
cursor.execute(req) cursor.execute(req)
res = cursor.fetchall() res = cursor.fetchall()
for data in res: for data in res:
if not data[7]:
unknow_files += 1
else:
img_date = datetime.strptime(data[7], "%Y:%m:%d %H:%M:%S") img_date = datetime.strptime(data[7], "%Y:%m:%d %H:%M:%S")
if start <= img_date <= end: if start <= img_date <= end:
catalog.update({ catalog.update({
@ -76,6 +80,7 @@ def extractor(dt_file, start, end):
dates.update({date: dates_list.count(date)}) dates.update({date: dates_list.count(date)})
return { return {
"total": len(catalog.keys()), "total": len(catalog.keys()),
"unknows": unknow_files,
"date": dates, "date": dates,
"cameras": cameras, "cameras": cameras,
"lenses": lenses, "lenses": lenses,

130
exv.py
View File

@ -1,7 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import piexif
from subprocess import Popen, PIPE
from datetime import datetime from datetime import datetime
import json import json
from os.path import isfile, isdir from os.path import isfile, isdir
@ -10,26 +9,11 @@ from os import walk
TYPES = [ TYPES = [
"JPEG", "JPEG",
"JPG", "JPG",
"EXV",
"CR2", "CR2",
"CRW",
"MRW",
"TIFF", "TIFF",
"TIF", "TIF",
"WEBP",
"DNG", "DNG",
"NEF", "NEF",
"FEF",
"ARW",
"RW2",
"SR2",
"SRW",
"ORF",
"PNG",
"PGF",
"RAF",
"PSD",
"JP2"
] ]
def extractor(input_files, start, end, recursive, extensions): def extractor(input_files, start, end, recursive, extensions):
@ -39,21 +23,21 @@ def extractor(input_files, start, end, recursive, extensions):
raise ValueError("Input files must be a list.") raise ValueError("Input files must be a list.")
exif_dict_list = [] exif_dict_list = []
files = [] files = []
usefull_exif = [ usefull_exif = {
"Image.Model", "Model": "",
"Photo.LensModel", "LensModel": "",
"Photo.FocalLength", "FocalLength": 0,
"Photo.ApertureValue", "ApertureValue": 0,
"Photo.ExposureTime", "ExposureTime": 0,
"Photo.ISOSpeedRatings", "ISOSpeedRatings": 0,
"Photo.PixelXDimension", "PixelXDimension": 0,
"Photo.PixelYDimension", "PixelYDimension": 0,
"Image.ImageWidth", "ImageWidth": 0,
"Image.ImageLength", "ImageLength": 0,
"Photo.DateTimeOriginal", "Pixels": None,
"Photo.DateTimeDigitized", "Dimension": 0,
"Exif.Image.DateTime" "DateTime": None,
] }
for item in input_files: for item in input_files:
if isdir(item): if isdir(item):
for (dirpath, dirnames, filenames) in walk(item): for (dirpath, dirnames, filenames) in walk(item):
@ -74,60 +58,46 @@ def extractor(input_files, start, end, recursive, extensions):
input_files = files input_files = files
for input_file in input_files: for input_file in input_files:
exif_dict = {} exif_dict = usefull_exif
if not isfile(input_file): if not isfile(input_file):
raise ValueError("{} doesn't exist here.".format(input_file)) raise ValueError("{} doesn't exist here.".format(input_file))
exif_dict.update({'file': input_file}) exif_dict.update({'file': input_file})
for extracted in usefull_exif: exif_tags = piexif.load(input_file)
cmd = ["exiv2", "-g", extracted, "-Pt", input_file] if 0x0110 in exif_tags['0th']:
with Popen(cmd, stdout=PIPE) as extracted_data: exif_dict.update({'Model':
extracted_data = extracted_data.stdout.read().decode() exif_tags['0th'][0x0110].decode()})
if extracted_data: if 0xa434 in exif_tags['Exif']:
extracted_data = extracted_data.splitlines()[0] exif_dict.update({'LensModel':
exif_dict.update({extracted.split(".")[-1]: extracted_data}) exif_tags['Exif'][0xa434].decode().split("\x00")[0]})
else: if 0x920a in exif_tags['Exif']:
exif_dict.update({extracted.split(".")[-1]: None}) exif_dict.update({'FocalLength':
if exif_dict['FocalLength']: float(exif_tags['Exif'][0x920a][0] / exif_tags['Exif'][0x920a][1])})
exif_dict['FocalLength'] = float( if 0x829d in exif_tags['Exif']:
exif_dict['FocalLength'].replace(" mm", "")) exif_dict.update({'ApertureValue':
if exif_dict['ApertureValue']: float(exif_tags['Exif'][0x829d][0]/exif_tags['Exif'][0x829d][1])})
exif_dict['ApertureValue'] = float( if 0x829a in exif_tags['Exif']:
exif_dict['ApertureValue'].replace("F", "")) exif_dict.update({'ExposureTime':
if exif_dict['PixelXDimension'] and exif_dict['PixelYDimension']: float(exif_tags['Exif'][0x829a][0]/exif_tags['Exif'][0x829a][1])})
exif_dict['PixelXDimension'] = int(exif_dict['PixelXDimension']) if 0x8827 in exif_tags['Exif']:
exif_dict['PixelYDimension'] = int(exif_dict['PixelYDimension']) exif_dict.update({'ISOSpeedRatings':
pixels = exif_dict['PixelXDimension'] * \ int(exif_tags['Exif'][0x8827])})
exif_dict['PixelYDimension'] if 0xa002 in exif_tags['Exif'] and 0xa003 in exif_tags['Exif']:
elif exif_dict['ImageLength'] and exif_dict['ImageWidth']: exif_dict.update({'Pixels':
exif_dict['PixelXDimension'] = int(exif_dict['ImageLength']) int(exif_tags['Exif'][0xa002]) * int(exif_tags['Exif'][0xa003])})
exif_dict['PixelYDimension'] = int(exif_dict['ImageWidth']) elif 0x0100 in exif_tags['0th'] and 0x0101 in exif_tags['0th']:
pixels = exif_dict['PixelXDimension'] * \ exif_dict.update({'Pixels':
exif_dict['PixelYDimension'] int(exif_tags['0th'][0x0100]) * int(exif_tags['0th'][0x0101])})
else: if 0x9003 in exif_tags['Exif']:
pixels = None
if exif_dict['DateTimeOriginal']:
exif_dict['DateTime'] = datetime.strptime( exif_dict['DateTime'] = datetime.strptime(
exif_dict['DateTimeOriginal'], "%Y:%m:%d %H:%M:%S") exif_tags['Exif'][0x9003].decode(), "%Y:%m:%d %H:%M:%S")
elif exif_dict['DateTimeDigitized']: elif 0x9004 in exif_tags['Exif']:
exif_dict['DateTime'] = datetime.strptime( exif_dict['DateTime'] = datetime.strptime(
exif_dict['DateTimeDigitized'], "%Y:%m:%d %H:%M:%S") exif_tags['Exif'][0x9004].decode(), "%Y:%m:%d %H:%M:%S")
elif exif_dict['DateTime']: elif 0x0132 in exif_tags['0th']:
exif_dict['DateTime'] = datetime.strptime( exif_dict['DateTime'] = datetime.strptime(
exif_dict['DateTime'], "%Y:%m:%d %H:%M:%S") exif_tags['0th'][0x0132].decode(), "%Y:%m:%d %H:%M:%S")
else: if exif_dict['Pixels']:
exif_dict['DateTime'] = None exif_dict.update({'Dimension': round((exif_dict['Pixels']/10**6), 1)})
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_dict_list.append(exif_dict) exif_dict_list.append(exif_dict)
cameras, lenses, focals, apertures, exposures = {}, {}, {}, {}, {} cameras, lenses, focals, apertures, exposures = {}, {}, {}, {}, {}
isos, dimensions, cameras_lenses, dates = {}, {}, {}, {} isos, dimensions, cameras_lenses, dates = {}, {}, {}, {}

View File

@ -2,3 +2,4 @@ Flask
numpy numpy
matplotlib matplotlib
babel 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
}