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,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,

162
exv.py
View File

@ -1,83 +1,110 @@
#!/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 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())): if not isinstance(input_files, type(list())):
raise ValueError("Input files must be a list.") raise ValueError("Input files must be a list.")
exif_dict_list = [] exif_dict_list = []
usefull_exif = [ files = []
"Image.Model", usefull_exif = {
"Photo.LensModel", "Model": "",
"Photo.FocalLength", "LensModel": "",
"Photo.ApertureValue", "FocalLength": 0,
"Photo.ExposureTime", "ApertureValue": 0,
"Photo.ISOSpeedRatings", "ExposureTime": 0,
"Photo.PixelXDimension", "ISOSpeedRatings": 0,
"Photo.PixelYDimension", "PixelXDimension": 0,
"Image.ImageWidth", "PixelYDimension": 0,
"Image.ImageLength", "ImageWidth": 0,
"Photo.DateTimeOriginal" "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: 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 exif_dict['DateTime'] = datetime.strptime(
if exif_dict['DateTimeOriginal']: exif_tags['Exif'][0x9003].decode(), "%Y:%m:%d %H:%M:%S")
exif_dict['DateTimeOriginal'] = datetime.strptime( elif 0x9004 in exif_tags['Exif']:
exif_dict['DateTimeOriginal'], "%Y:%m:%d %H:%M:%S") exif_dict['DateTime'] = datetime.strptime(
if exif_dict['ExposureTime']: exif_tags['Exif'][0x9004].decode(), "%Y:%m:%d %H:%M:%S")
try: elif 0x0132 in exif_tags['0th']:
exif_dict['ExposureTime'] = float( exif_dict['DateTime'] = datetime.strptime(
exif_dict['ExposureTime'].split(" ")[0]) exif_tags['0th'][0x0132].decode(), "%Y:%m:%d %H:%M:%S")
except ValueError: if exif_dict['Pixels']:
exposure = exif_dict['ExposureTime'].split(" ")[0] exif_dict.update({'Dimension': round((exif_dict['Pixels']/10**6), 1)})
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 = {}, {}, {}, {}
cameras_list, lenses_list, focals_list, apertures_list, exposures_list = [], [], [], [], [] cameras_list, lenses_list, focals_list, apertures_list, exposures_list = [], [], [], [], []
isos_list, dimensions_list, cameras_lenses_list, dates_list = [], [], [], [] isos_list, dimensions_list, cameras_lenses_list, dates_list = [], [], [], []
for data in exif_dict_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']) cameras_list.append(data['Model'])
lenses_list.append(data['LensModel']) lenses_list.append(data['LensModel'])
focals_list.append(data['FocalLength']) focals_list.append(data['FocalLength'])
@ -88,7 +115,7 @@ def extractor(input_files, start, end):
if data['LensModel']: if data['LensModel']:
cameras_lenses_list.append( cameras_lenses_list.append(
"{} + {}".format(data['Model'], data['LensModel'])) "{} + {}".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)): for camera in list(set(cameras_list)):
if camera: if camera:
cameras.update({camera: cameras_list.count(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='+') "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("-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("-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() 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: 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. 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: if not args.end_date:
@ -150,4 +185,5 @@ if __name__ == '__main__':
except ValueError: except ValueError:
print("Date must be YYYYMMDD.") print("Date must be YYYYMMDD.")
exit() 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 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
}