Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
82782ee819 | |||
6db8416d1c | |||
c6466b4fbb | |||
fa701c1ae7 | |||
cfc322a0fc | |||
0a4a75647a | |||
2dce5901e3 | |||
2684926e15 |
44
README.md
44
README.md
@ -1,20 +1,27 @@
|
|||||||
# PhotoReport
|
# PhotoReport
|
||||||
|
|
||||||
## Dependencies
|
## Dépendences
|
||||||
|
|
||||||
- Python 3 (and some additionnals modules, see 'requirements.txt')
|
- Python 3
|
||||||
- rst2pdf
|
|
||||||
- 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 +32,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 qu’il sera possible de filtrer par objectif/boîtier, parce qu’au 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, l’option -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
|
|
||||||
|
5
dtlib.py
5
dtlib.py
@ -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,
|
||||||
|
132
exv.py
132
exv.py
@ -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,61 +58,47 @@ 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.copy()
|
||||||
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']:
|
exif_dict_list.append(exif_dict.copy())
|
||||||
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)
|
|
||||||
cameras, lenses, focals, apertures, exposures = {}, {}, {}, {}, {}
|
cameras, lenses, focals, apertures, exposures = {}, {}, {}, {}, {}
|
||||||
isos, dimensions, cameras_lenses, dates = {}, {}, {}, {}
|
isos, dimensions, cameras_lenses, dates = {}, {}, {}, {}
|
||||||
cameras_list, lenses_list, focals_list, apertures_list, exposures_list = [], [], [], [], []
|
cameras_list, lenses_list, focals_list, apertures_list, exposures_list = [], [], [], [], []
|
||||||
|
@ -2,3 +2,4 @@ Flask
|
|||||||
numpy
|
numpy
|
||||||
matplotlib
|
matplotlib
|
||||||
babel
|
babel
|
||||||
|
piexif
|
||||||
|
187
sortie_exemple.json
Normal file
187
sortie_exemple.json
Normal 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
|
||||||
|
}
|
Reference in New Issue
Block a user