Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
0a4a75647a | |||
2dce5901e3 | |||
2684926e15 | |||
17ce459630 | |||
ed525ccf2f | |||
6e702b3b3b | |||
664c8957fe | |||
f558b6574a | |||
62d759ab41 | |||
3fca8eb458 | |||
6962085961 | |||
1ea48e5af5 | |||
e91d51a43f | |||
47a2baf847 |
17
README
17
README
@ -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
47
README.md
Normal 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
100
exv.py
@ -4,13 +4,41 @@
|
||||
from subprocess import Popen, PIPE
|
||||
from datetime import datetime
|
||||
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())):
|
||||
raise ValueError("Input files must be a list.")
|
||||
exif_dict_list = []
|
||||
files = []
|
||||
usefull_exif = [
|
||||
"Image.Model",
|
||||
"Photo.LensModel",
|
||||
@ -22,22 +50,49 @@ def extractor(input_files, start, end):
|
||||
"Photo.PixelYDimension",
|
||||
"Image.ImageWidth",
|
||||
"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:
|
||||
exif_dict = {}
|
||||
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]
|
||||
cmd = ["exiv2", "-Ptk", 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})
|
||||
for line in extracted_data.splitlines():
|
||||
for exif in usefull_exif:
|
||||
if exif in line.split()[0]:
|
||||
exif_dict.update(
|
||||
{
|
||||
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']:
|
||||
exif_dict['FocalLength'] = float(
|
||||
exif_dict['FocalLength'].replace(" mm", ""))
|
||||
@ -57,8 +112,16 @@ def extractor(input_files, start, end):
|
||||
else:
|
||||
pixels = None
|
||||
if exif_dict['DateTimeOriginal']:
|
||||
exif_dict['DateTimeOriginal'] = datetime.strptime(
|
||||
exif_dict['DateTime'] = datetime.strptime(
|
||||
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']:
|
||||
try:
|
||||
exif_dict['ExposureTime'] = float(
|
||||
@ -77,7 +140,7 @@ def extractor(input_files, start, end):
|
||||
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 +151,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 +202,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 +221,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))
|
||||
|
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