14 Commits

4 changed files with 322 additions and 33 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

100
exv.py
View File

@ -4,13 +4,41 @@
from subprocess import Popen, PIPE 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",
"EXV",
"CR2",
"CRW",
"MRW",
"TIFF",
"TIF",
"WEBP",
"DNG",
"NEF",
"FEF",
"ARW",
"RW2",
"SR2",
"SRW",
"ORF",
"PNG",
"PGF",
"RAF",
"PSD",
"JP2"
]
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 = []
files = []
usefull_exif = [ usefull_exif = [
"Image.Model", "Image.Model",
"Photo.LensModel", "Photo.LensModel",
@ -22,22 +50,49 @@ def extractor(input_files, start, end):
"Photo.PixelYDimension", "Photo.PixelYDimension",
"Image.ImageWidth", "Image.ImageWidth",
"Image.ImageLength", "Image.ImageLength",
"Photo.DateTimeOriginal" "Photo.DateTimeOriginal",
"Photo.DateTimeDigitized",
"Exif.Image.DateTime"
] ]
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 = {}
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: cmd = ["exiv2", "-Ptk", input_file]
cmd = ["exiv2", "-g", extracted, "-Pt", input_file]
with Popen(cmd, stdout=PIPE) as extracted_data: with Popen(cmd, stdout=PIPE) as extracted_data:
extracted_data = extracted_data.stdout.read().decode() extracted_data = extracted_data.stdout.read().decode()
if extracted_data: for line in extracted_data.splitlines():
extracted_data = extracted_data.splitlines()[0] for exif in usefull_exif:
exif_dict.update({extracted.split(".")[1]: extracted_data}) if exif in line.split()[0]:
else: exif_dict.update(
exif_dict.update({extracted.split(".")[1]: None}) {
line.split()[0].split(".")[-1]:
" ".join(line.split()[1:])
}
)
for exif in usefull_exif:
if exif.split(".")[-1] not in exif_dict:
exif_dict.update({exif.split(".")[-1]: None})
if exif_dict['FocalLength']: if exif_dict['FocalLength']:
exif_dict['FocalLength'] = float( exif_dict['FocalLength'] = float(
exif_dict['FocalLength'].replace(" mm", "")) exif_dict['FocalLength'].replace(" mm", ""))
@ -57,8 +112,16 @@ def extractor(input_files, start, end):
else: else:
pixels = None pixels = None
if exif_dict['DateTimeOriginal']: if exif_dict['DateTimeOriginal']:
exif_dict['DateTimeOriginal'] = datetime.strptime( exif_dict['DateTime'] = datetime.strptime(
exif_dict['DateTimeOriginal'], "%Y:%m:%d %H:%M:%S") exif_dict['DateTimeOriginal'], "%Y:%m:%d %H:%M:%S")
elif exif_dict['DateTimeDigitized']:
exif_dict['DateTime'] = datetime.strptime(
exif_dict['DateTimeDigitized'], "%Y:%m:%d %H:%M:%S")
elif exif_dict['DateTime']:
exif_dict['DateTime'] = datetime.strptime(
exif_dict['DateTime'], "%Y:%m:%d %H:%M:%S")
else:
exif_dict['DateTime'] = None
if exif_dict['ExposureTime']: if exif_dict['ExposureTime']:
try: try:
exif_dict['ExposureTime'] = float( exif_dict['ExposureTime'] = float(
@ -77,7 +140,7 @@ def extractor(input_files, start, end):
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 +151,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 +202,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 +221,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))

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
}