Compare commits

...

29 Commits

Author SHA1 Message Date
3e6c235e0b HTML readme 2021-12-04 22:56:35 +01:00
c0a329c950 Ne crash plus s'il n'y a pas d'image pour l'APN et/ou l'objectif 2021-12-04 22:56:35 +01:00
8a1c0f0780 exv.py : mise à jour de la liste avec .copy() (sinon les dictionnaires sont mis à jour dans la liste également...) (2) 2021-12-04 22:56:35 +01:00
40a4b3e001 exv.py : mise à jour de la liste avec .copy() (sinon les dictionnaires sont mis à jour dans la liste également...) 2021-12-04 22:56:35 +01:00
6ed94deb38 Suppression de la dépendance à exiv2 2021-12-04 22:56:35 +01:00
9fa8c6e412 exv.py : Utilisation de piexif (ajouté à "requirements.txt"). Merci à Christophe pour l'idée 2021-12-04 22:56:35 +01:00
00270fdc18 dtlib.py : ne crash pas si une image n'a pas de donnée exif 2021-12-04 22:56:35 +01:00
557719baf6 Optimisation lors du traitement des tags : gain x10 en temps d'exécution 2021-12-04 22:56:35 +01:00
1bf72fcd34 Erreur de syntax MarkDown 2021-12-04 22:56:35 +01:00
b28b6214b0 Version plus claire du README 2021-12-04 22:56:35 +01:00
47b87835f9 Now reads only specified files if extension is provided. 2021-12-04 22:56:35 +01:00
bb90e192e9 bugfix : try to use different exif tag for datetime 2021-12-04 22:56:35 +01:00
e40f546137 Analyze only exif enabled files 2021-12-04 22:56:35 +01:00
4834f8fa6e Recursive if directory 2021-12-04 22:56:35 +01:00
0111140907 Modification de la FR pour LaTeX 2021-12-04 22:56:35 +01:00
8543903730 Feature request : LaTeX 2021-12-04 22:56:35 +01:00
a897021d03 Ajout de "Comment contribuer ?" 2021-12-04 22:56:35 +01:00
dff8defd13 Ajout des features requests 2021-12-04 22:56:35 +01:00
860dc2b282 Suppression d'une ligne inutile 2021-12-04 22:56:35 +01:00
a45d59c277 README en MarkDown 2021-12-04 22:56:35 +01:00
8cc52bdfd5 Updates 2021-12-04 22:56:35 +01:00
b767aa35c0 Ajout du script pour lire directement les exif 2021-12-04 22:56:35 +01:00
c263096242 Requirements 2021-12-04 22:56:35 +01:00
36270c21b1 Add install how-to and dependancies 2021-12-04 22:56:35 +01:00
7ff17d00b2 Update Canon EOS 1100D and Canon EF 50mm 1.8 pictures 2021-12-04 22:56:35 +01:00
7c41436541 Use « montage » for picture with camera and lens 2021-12-04 22:56:35 +01:00
ba384dd41b UTF-8 2021-12-04 22:56:35 +01:00
fb36633e8c First commit, working CLI 2021-12-04 22:56:35 +01:00
48c156b8ce Import 2021-12-04 22:56:20 +01:00
25 changed files with 1299 additions and 149 deletions

140
.gitignore vendored
View File

@ -1,140 +0,0 @@
# ---> Python
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/

View File

@ -1,9 +0,0 @@
MIT License
Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

40
README.html Normal file
View File

@ -0,0 +1,40 @@
<h1>PhotoReport</h1>
<h2>Dépendences</h2>
<ul>
<li>Python 3</li>
</ul>
<h2>Installation</h2>
<ul>
<li>Créer un environnement virtuel python3 (e.g. : <code>virtulenv3 PhotoReport &amp;&amp; source bin/activate</code>)</li>
<li>Cloner le dépôts (<code>git clone https://git.antoineve.me/PhotoReport/</code>)</li>
<li>Installer les dépendances (Note : pour l'instant, version 0.1, ce n'est pas nécessaire)</li>
</ul>
<h2>Utilisation en ligne de commande</h2>
<h3>exv.py</h3>
<p>L'aide est disponible avec <code>./exv.py --help</code>.</p>
<p>Exemple d'utilisation :</p>
<pre><code>./exv.py -R -x jpg ~/Nextcloud/Photos/2019/
</code></pre>
<p>Exemple de sortie : <a href="https://git.antoineve.me/PhotoReport/tree/sortie_exemple.json">sortie_exemple.json</a></p>
<h2>Comment contribuer ?</h2>
<ul>
<li>Cloner le dépôt : <code>git clone https://git.antoineve.me/PhotoReport/</code></li>
<li>Créer une branche : <code>git checkout -b &lt;new_feature&gt;</code></li>
<li>Coder et commiter : <code>git commit -a -m &lt;description&gt;</code></li>
<li>Créer le patch : <code>git format-patch $(git merge-base --fork-point master)..&lt;new_feature&gt;</code></li>
<li>M'envoyer le patch par mail : <a href="mailto:antoine+photoreport@van-elstraete.net">antoine+photoreport@van-elstraete.net</a></li>
<li>J'applique le patch avec <code>git am --signoff -k &lt; &lt;new_feature&gt;.patch</code></li>
</ul>
<h2>BUGS &amp; TODO</h2>
<ul>
<li>Pour le moment, pas de sortie en PDF</li>
<li>La base de donnée de darktable a changé depuis la première écriture du script</li>
<li>La vitesse d'obturation n'est pas arrondie, est-ce souhaitable ? (16.666 ≠ 16.666666666666668 ?)</li>
</ul>
<h2>Features requests et développement prévu</h2>
<ul>
<li>Créer une option pour filtrer par objectif <strong>-&gt; branche filters/lens</strong></li>
<li>Créer une option pour filtrer par boitier <strong>-&gt; branche filters/camera</strong></li>
<li>Sortie PDF fonctionnelle <strong>-&gt; branche output/pdf_via_md</strong></li>
<li>Créer une option pour une sortie exploitable par LaTeX</li>
</ul>

View File

@ -1,2 +1,46 @@
# PhotoReport
## Dépendences
- Python 3
## 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

114
dtlib.py Executable file
View File

@ -0,0 +1,114 @@
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import sqlite3
from datetime import datetime
def extractor(dt_file, start, end):
catalog = {}
unknow_files = 0
conn = sqlite3.connect(dt_file)
cursor = conn.cursor()
req = "SELECT id, model, lens, exposure, aperture, iso, focal_length, datetime_taken, width, height FROM images;"
cursor.execute(req)
res = cursor.fetchall()
for data in res:
if not data[7]:
unknow_files += 1
else:
img_date = datetime.strptime(data[7], "%Y:%m:%d %H:%M:%S")
if start <= img_date <= end:
catalog.update({
data[0]:
{
'camera': data[1],
'lens': data[2],
'shutter': float(data[3]*1000),
'aperture': round(float(data[4]), 1),
'iso': int(data[5]),
'focal': float(data[6]),
'datetime': img_date,
'height': int(data[9]),
'width': int(data[8])
}
})
cameras, lenses, focals, apertures, shutter_speeds = {}, {}, {}, {}, {}
isos, dimensions, cameras_lenses, dates = {}, {}, {}, {}
cameras_list, lenses_list, focals_list, apertures_list, shutter_speeds_list = [], [], [], [], []
isos_list, dimensions_list, cameras_lenses_list, dates_list = [], [], [], []
for item in catalog.keys():
cameras_list.append(catalog[item]['camera'])
lenses_list.append(catalog[item]['lens'])
if catalog[item]['lens'] and "---" not in catalog[item]['lens']:
cameras_lenses_list.append(
"{} + {}".format(catalog[item]['camera'], catalog[item]['lens']))
focals_list.append(round(float(catalog[item]['focal']), 1))
apertures_list.append(catalog[item]['aperture'])
shutter_speeds_list.append(catalog[item]['shutter'])
isos_list.append(catalog[item]['iso'])
dimensions_list.append(
round(((catalog[item]['height']*catalog[item]['width'])/10**6), 1))
dates_list.append(catalog[item]['datetime'].strftime("%Y%m%d"))
for camera in list(set(cameras_list)):
if camera:
cameras.update({camera: cameras_list.count(camera)})
for lens in list(set(lenses_list)):
if lens and "---" not in lens:
lenses.update({lens: lenses_list.count(lens)})
for camera_lense in list(set(cameras_lenses_list)):
if camera_lense:
cameras_lenses.update(
{camera_lense: cameras_lenses_list.count(camera_lense)})
for focal in list(set(focals_list)):
if focal:
focals.update({focal: focals_list.count(focal)})
for aperture in list(set(apertures_list)):
if aperture:
apertures.update({aperture: apertures_list.count(aperture)})
for shutter_speed in list(set(shutter_speeds_list)):
if shutter_speed:
shutter_speeds.update(
{shutter_speed: shutter_speeds_list.count(shutter_speed)})
for iso in list(set(isos_list)):
if iso:
isos.update({iso: isos_list.count(iso)})
for dimension in list(set(dimensions_list)):
if dimension:
dimensions.update({dimension: dimensions_list.count(dimension)})
for date in list(set(dates_list)):
if date:
dates.update({date: dates_list.count(date)})
return {
"total": len(catalog.keys()),
"unknows": unknow_files,
"date": dates,
"cameras": cameras,
"lenses": lenses,
"cameras+lenses": cameras_lenses,
"focals": focals,
"apertures": apertures,
"shutter_speeds": shutter_speeds,
"isos": isos,
"dimensions": dimensions
}
if __name__ == '__main__':
import argparse
import json
parser = argparse.ArgumentParser()
parser.add_argument("infile", help="darktable library")
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)
args = parser.parse_args()
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:
args.end_date = datetime.strftime(datetime.now(), "%Y%m%d")
try:
datetime.strptime(args.start_date, "%Y%m%d")
datetime.strptime(args.end_date, "%Y%m%d")
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))

189
exv.py Executable file
View File

@ -0,0 +1,189 @@
#!/usr/bin/env python3
import piexif
from datetime import datetime
import json
from os.path import isfile, isdir
from os import walk
TYPES = [
"JPEG",
"JPG",
"CR2",
"TIFF",
"TIF",
"DNG",
"NEF",
]
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 = {
"Model": "",
"LensModel": "",
"FocalLength": 0,
"ApertureValue": 0,
"ExposureTime": 0,
"ISOSpeedRatings": 0,
"PixelXDimension": 0,
"PixelYDimension": 0,
"ImageWidth": 0,
"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:
exif_dict = usefull_exif.copy()
if not isfile(input_file):
raise ValueError("{} doesn't exist here.".format(input_file))
exif_dict.update({'file': input_file})
exif_tags = piexif.load(input_file)
if 0x0110 in exif_tags['0th']:
exif_dict.update({'Model':
exif_tags['0th'][0x0110].decode()})
if 0xa434 in exif_tags['Exif']:
exif_dict.update({'LensModel':
exif_tags['Exif'][0xa434].decode().split("\x00")[0]})
if 0x920a in exif_tags['Exif']:
exif_dict.update({'FocalLength':
float(exif_tags['Exif'][0x920a][0] / exif_tags['Exif'][0x920a][1])})
if 0x829d in exif_tags['Exif']:
exif_dict.update({'ApertureValue':
float(exif_tags['Exif'][0x829d][0]/exif_tags['Exif'][0x829d][1])})
if 0x829a in exif_tags['Exif']:
exif_dict.update({'ExposureTime':
float(exif_tags['Exif'][0x829a][0]/exif_tags['Exif'][0x829a][1])})
if 0x8827 in exif_tags['Exif']:
exif_dict.update({'ISOSpeedRatings':
int(exif_tags['Exif'][0x8827])})
if 0xa002 in exif_tags['Exif'] and 0xa003 in exif_tags['Exif']:
exif_dict.update({'Pixels':
int(exif_tags['Exif'][0xa002]) * int(exif_tags['Exif'][0xa003])})
elif 0x0100 in exif_tags['0th'] and 0x0101 in exif_tags['0th']:
exif_dict.update({'Pixels':
int(exif_tags['0th'][0x0100]) * int(exif_tags['0th'][0x0101])})
if 0x9003 in exif_tags['Exif']:
exif_dict['DateTime'] = datetime.strptime(
exif_tags['Exif'][0x9003].decode(), "%Y:%m:%d %H:%M:%S")
elif 0x9004 in exif_tags['Exif']:
exif_dict['DateTime'] = datetime.strptime(
exif_tags['Exif'][0x9004].decode(), "%Y:%m:%d %H:%M:%S")
elif 0x0132 in exif_tags['0th']:
exif_dict['DateTime'] = datetime.strptime(
exif_tags['0th'][0x0132].decode(), "%Y:%m:%d %H:%M:%S")
if exif_dict['Pixels']:
exif_dict.update({'Dimension': round((exif_dict['Pixels']/10**6), 1)})
exif_dict_list.append(exif_dict.copy())
cameras, lenses, focals, apertures, exposures = {}, {}, {}, {}, {}
isos, dimensions, cameras_lenses, dates = {}, {}, {}, {}
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 data['DateTime'] and start <= data['DateTime'] <= end:
cameras_list.append(data['Model'])
lenses_list.append(data['LensModel'])
focals_list.append(data['FocalLength'])
apertures_list.append(data['ApertureValue'])
exposures_list.append(data['ExposureTime'])
isos_list.append(data['ISOSpeedRatings'])
dimensions_list.append(data['Dimension'])
if data['LensModel']:
cameras_lenses_list.append(
"{} + {}".format(data['Model'], data['LensModel']))
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)})
for lens in list(set(lenses_list)):
if lens:
lenses.update({lens: lenses_list.count(lens)})
for camera_lens in list(set(cameras_lenses_list)):
if camera_lens:
cameras_lenses.update(
{camera_lens: cameras_lenses_list.count(camera_lens)})
for focal in list(set(focals_list)):
if focal:
focals.update({focal: focals_list.count(focal)})
for aperture in list(set(apertures_list)):
if aperture:
apertures.update({aperture: apertures_list.count(aperture)})
for exposure in list(set(exposures_list)):
if exposure:
exposures.update(
{float(exposure*1000): exposures_list.count(exposure)})
for iso in list(set(isos_list)):
if iso:
isos.update({iso: isos_list.count(iso)})
for dimension in list(set(dimensions_list)):
if dimension:
dimensions.update({dimension: dimensions_list.count(dimension)})
for date in list(set(dates_list)):
if date:
dates.update({date: dates_list.count(date)})
return {
"total": len(dimensions_list),
"date": dates,
"cameras": cameras,
"lenses": lenses,
"cameras+lenses": cameras_lenses,
"focals": focals,
"apertures": apertures,
"shutter_speeds": exposures,
"isos": isos,
"dimensions": dimensions
}
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser()
parser.add_argument(
"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:
args.end_date = datetime.strftime(datetime.now(), "%Y%m%d")
try:
datetime.strptime(args.start_date, "%Y%m%d")
datetime.strptime(args.end_date, "%Y%m%d")
except ValueError:
print("Date must be YYYYMMDD.")
exit()
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))

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

1
helpers/dictionary.json Normal file
View File

@ -0,0 +1 @@
{"camera": {"ILCE-QX1": {"brand": "Sony", "name": "QX1"}, "ILCE-6500": {"brand": "Sony", "name": "\u03b16500"}, "ILCE-5100": {"brand": "Sony", "name": "\u03b15100"}, "ILCA-77M2": {"brand": "Sony", "name": "\u03b177 II"}, "DSC-RX100M3": {"brand": "Sony", "name": "RX100 III"}, "Canon EOS 1100D": {"brand": "Canon", "name": "EOS 1100D"}, "FIG-LX1": {"brand": "Huawei", "name": "P Smart"}}, "lens": {"E 55-210mm F4.5-6.3 OSS": {"brand": "Sony", "name": "E 55-210mm F4.5-6.3 OSS"}, "10-20mm F3.5": {"brand": "Sigma", "name": "10-20mm F3.5 EX DC HSM"}, "105mm F2.8": {"brand": "Sigma", "name": "MACRO 105mm F2.8 EX DG OS HSM"}, "50mm F1.4": {"brand": "Sony", "name": "50mm F1.4"}, "24-70mm F1.8-2.8": {"brand": "ZEISS", "name": "Vario-Sonnar T* 24-70 mm F1.8-2.8"}, "70-300mm F4.5-5.6 G SSM": {"brand": "Sony", "name": "70\u2013300 mm F4.5\u20135.6 G SSM"}, "E PZ 18-105mm F4 G OSS": {"brand": "Sony", "name": "E PZ 18\u2013105 mm F4 G OSS"}, "FE 90mm F2.8 Macro G OSS": {"brand": "Sony", "name": "FE 90 mm F2.8 Macro G OSS"}, "Canon EF 50mm f/1.8 STM": {"brand": "Canon", "name": "Canon EF 50mm f/1.8 STM"}, "Canon EF-S 18-55mm f/3.5-5.6 IS II": {"brand": "Canon", "name": "EF-S 18-55mm f/3.5-5.6 IS II"}}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -0,0 +1,187 @@
msgid ""
msgstr ""
"Project-Id-Version: PhotoReport 0.1\n"
"POT-Creation-Date: 2018-11-13 21:30+0100\n"
"PO-Revision-Date: 2018-11-13 21:30+0100\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: fr_FR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.2\n"
"X-Poedit-Basepath: ../../..\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Poedit-SearchPath-0: report.py\n"
#: report.py:81
msgid "Statistical photo report"
msgstr "Rapport statistique des photos"
#: report.py:95
msgid "Pictures informations"
msgstr "Informations sur les photos"
#: report.py:104
msgid "The set contains {} pictures, "
msgstr "La collection comprend {} images, "
#: report.py:106
msgid "The set contains only one picture, "
msgstr "La collection comprend une seule image, "
#: report.py:108
msgid "The set is empty. "
msgstr "La collection est vide. "
#: report.py:110
msgid "taken from {} to {}. "
msgstr "prisent du {} au {}. "
#: report.py:113
msgid "The most productive day was {} ({} shots). "
msgstr "La journée la plus productive fut le {} ({} prises de vue). "
#: report.py:117
msgid "taken on {}. "
msgstr "prise le {}. "
#: report.py:120
#, python-brace-format
msgid "Pictures sizes are from {1}{0}Mpx to {2}{0}Mpx, mostly {3}{0}Mpx. "
msgstr ""
"La taille des images varie de {1}{0}Mpx à {2}{0}Mpx, pour la plupart à {3}{0}"
"Mpx. "
#: report.py:126
msgid "Picture size is {}{}Mpx. "
msgstr "La dimension de l'image est de {}{}Mpx. "
#: report.py:140
msgid "Distribution of shots by date{}"
msgstr "Répartition des photos par dates{}"
#: report.py:140 report.py:253 report.py:277 report.py:308 report.py:332
msgid " : top 20"
msgstr " : top 20"
#: report.py:141 report.py:252 report.py:276 report.py:307 report.py:331
msgid "Number of shots"
msgstr "Nombre de prises de vue"
#: report.py:156
msgid "Cameras & lenses"
msgstr "Appareils photo & objectifs"
#: report.py:164
msgid "The most commonly used couple is {} with {}. "
msgstr "Le couple le plus utilisé est le {} avec le {}. "
#: report.py:169
msgid "There are {} lenses, the most used is the {}. "
msgstr "Il y a {} objectifs, le plus utilisé est le {}. "
#: report.py:173
msgid "The lens is a {}. "
msgstr "L'objectif est un {}. "
#: report.py:175
msgid "The favorite aperture is {}{}, "
msgstr "L'ouverture favorite est {}{}, "
#: report.py:176 report.py:275 report.py:283
msgid "F"
msgstr "f/"
#: report.py:178
msgid "and the favorite focal lenght is {}mm. "
msgstr "et la longueur focal favorite est {} mm. "
#: report.py:182
msgid "There are {} cameras, the mose used is the {}. "
msgstr "Il y a {} appareils photos, le plus utilisé est le {}. "
#: report.py:186
msgid "The camera is a {}. "
msgstr "L'appareil photo est un {}. "
#: report.py:188
msgid " The favorite ISO is {}. "
msgstr " La sensibilité favorite est de {} ISO. "
#: report.py:210
msgid "Cameras"
msgstr "Appareils photo"
#: report.py:224
msgid "Lenses"
msgstr "Objectifs"
#: report.py:233
msgid "Focal lengths & apertures"
msgstr "Longueurs focales & ouvertures"
#: report.py:236
msgid "Focal lenghts"
msgstr "Longueurs focales"
#: report.py:253
msgid "Focals{}"
msgstr "Longueurs focales{}"
#: report.py:258
msgid "Only one focal lenght is used : {}mm. "
msgstr "Une seule longueur focal est utilisée : {}mm. "
#: report.py:260
msgid "Apertures"
msgstr "Ouvertures"
#: report.py:277
msgid "Apertures{}"
msgstr "Ouvertures{}"
#: report.py:282
msgid "Only one aperture is used : {}{}. "
msgstr "Une seule ouverture est utilisée : {}{}. "
#: report.py:289
msgid "ISO sensitivities & shutter speeds"
msgstr "Sensibilités ISO & vitesses d'obturation"
#: report.py:291
msgid "ISO sensitivities"
msgstr "Sensibilités ISO"
#: report.py:308
msgid "ISO sensibilities{}"
msgstr "Sensibilités ISO{}"
#: report.py:313
msgid "All shots at the same sensitivity : ISO {} "
msgstr "Toutes les photos ont été prise avec la même sensiblité : ISO {}. "
#: report.py:315
msgid "Shutter speeds"
msgstr "Vitesses d'obturation"
#: report.py:332
msgid "Shutter speeds{}"
msgstr "Vitesses d'obturation{}"
#: report.py:337
msgid "All shots at the same speed : {}. "
msgstr "Toutes les photos ont été prises à la même vitesse : {}. "
#~ msgid ""
#~ "There are {} lenses, the most used is the {}. The favorite aperture is {}"
#~ "{}, "
#~ msgstr ""
#~ "Il y a {} objectifs, le plus utilisé est le {}. L'ouverture favorite est "
#~ "{}{}, "
#~ msgid "Apertures : Top 20"
#~ msgstr "Ouvertures : top 20"
#~ msgid "Great !"
#~ msgstr "Super !"

116
lrcat.py Executable file
View File

@ -0,0 +1,116 @@
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import sqlite3
from datetime import datetime
def extractor(lrcat_file, start, end):
catalog, camera, lens = {}, {}, {}
conn = sqlite3.connect(lrcat_file)
cursor = conn.cursor()
req = "SELECT id_local, value FROM AgInternedExifCameraModel"
cursor.execute(req)
res = cursor.fetchall()
for data in res:
camera.update({data[0]: data[1]})
req = "SELECT id_local, value FROM AgInternedExifLens"
cursor.execute(req)
res = cursor.fetchall()
for data in res:
lens.update({data[0]: data[1]})
req = "SELECT image, focalLength, aperture, isoSpeedRating, shutterSpeed, cameraModelRef, lensRef FROM AgHarvestedExifMetadata"
cursor.execute(req)
res = cursor.fetchall()
for data in res:
if data[2]:
aperture = round(2**(data[2]/2), 1)
if data[4]:
shutter = round(1/(2**data[4])*1000, 4)
catalog.update({int(data[0]): {'focal': data[1], 'aperture': aperture, 'iso': int(
data[3]), 'shutter': shutter, 'camera': camera[data[5]], 'lens': lens[data[6]]}})
req = "SELECT id_local, captureTime, fileHeight, fileWidth FROM Adobe_images"
cursor.execute(req)
res = cursor.fetchall()
for data in res:
catalog[int(data[0])].update(
{'datetime': datetime.strptime(data[1], "%Y-%m-%dT%H:%M:%S"), 'height': int(data[2]), 'width': int(data[3])})
cameras, lenses, focals, apertures, shutter_speeds = {}, {}, {}, {}, {}
isos, dimensions, cameras_lenses, dates = {}, {}, {}, {}
cameras_list, lenses_list, focals_list, apertures_list, shutter_speeds_list = [], [], [], [], []
isos_list, dimensions_list, cameras_lenses_list, dates_list = [], [], [], []
for item in catalog.keys():
if start <= catalog[item]['datetime'] <= end:
cameras_list.append(catalog[item]['camera'])
lenses_list.append(catalog[item]['lens'])
if catalog[item]['lens'] and "---" not in catalog[item]['lens']:
cameras_lenses_list.append(
"{} + {}".format(catalog[item]['camera'], catalog[item]['lens']))
focals_list.append(catalog[item]['focal'])
apertures_list.append(catalog[item]['aperture'])
shutter_speeds_list.append(catalog[item]['shutter'])
isos_list.append(catalog[item]['iso'])
dimensions_list.append(
round(((catalog[item]['height']*catalog[item]['width'])/10**6), 1))
dates_list.append(catalog[item]['datetime'].strftime("%Y%m%d"))
for camera in list(set(cameras_list)):
if camera:
cameras.update({camera: cameras_list.count(camera)})
for lens in list(set(lenses_list)):
if lens and "---" not in lens:
lenses.update({lens: lenses_list.count(lens)})
for camera_lense in list(set(cameras_lenses_list)):
if camera_lense:
cameras_lenses.update(
{camera_lense: cameras_lenses_list.count(camera_lense)})
for focal in list(set(focals_list)):
if focal:
focals.update({focal: focals_list.count(focal)})
for aperture in list(set(apertures_list)):
if aperture:
apertures.update({aperture: apertures_list.count(aperture)})
for shutter_speed in list(set(shutter_speeds_list)):
if shutter_speed:
shutter_speeds.update(
{shutter_speed: shutter_speeds_list.count(shutter_speed)})
for iso in list(set(isos_list)):
if iso:
isos.update({iso: isos_list.count(iso)})
for dimension in list(set(dimensions_list)):
if dimension:
dimensions.update({dimension: dimensions_list.count(dimension)})
for date in list(set(dates_list)):
if date:
dates.update({date: dates_list.count(date)})
return {
"total": len(dimensions_list),
"date": dates,
"cameras": cameras,
"lenses": lenses,
"cameras+lenses": cameras_lenses,
"focals": focals,
"apertures": apertures,
"shutter_speeds": shutter_speeds,
"isos": isos,
"dimensions": dimensions
}
if __name__ == '__main__':
import argparse
import json
parser = argparse.ArgumentParser()
parser.add_argument("infile", help="lrcat file")
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)
args = parser.parse_args()
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:
args.end_date = datetime.strftime(datetime.now(), "%Y%m%d")
try:
datetime.strptime(args.start_date, "%Y%m%d")
datetime.strptime(args.end_date, "%Y%m%d")
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))

400
report.py Executable file
View File

@ -0,0 +1,400 @@
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import numpy as np
from matplotlib import pyplot as plt
import json
import uuid
import gettext
from datetime import date, datetime, timedelta
from babel.dates import format_date
import locale
from os.path import isfile
from tempfile import mkdtemp
from shutil import copyfile, rmtree
from subprocess import run, CalledProcessError
def i18n(language):
try:
lang = gettext.translation(
'report', localedir='locales', languages=[language])
lang.install()
except FileNotFoundError:
gettext.install('report')
def bar(data, filename):
'''
Draw a bar chart from a documented JSON
'''
font = {'fontname': 'DejaVu Sans', 'size': 10, 'weight': 'normal'}
x_location = np.arange(len(data['data']))
fig = plt.figure(figsize=(data['width']/2.54, data['height']/2.54))
bars = plt.bar(x_location, data['data'],
data['bar_width'], color=data['bar_color'])
plt.ylabel(data['Y_label'], **font, fontsize='x-small')
plt.title(data['title'], **font)
plt.xticks(x_location, data['data_labels'], **font, fontsize='x-small', rotation=45)
plt.yticks(**font, fontsize='x-small')
plt.ylim(0, max(data['data'])*1.15)
for bar in bars:
height = bar.get_height()
plt.text(bar.get_x() + bar.get_width()/2, height+(max(data['data'])/60), "{}\n({}%)".format(int(height), float(round(height/sum(data['data'])*100, 1))), ha='center', va='bottom', **font, fontsize='xx-small')
plt.tight_layout()
plt.savefig("{}".format(filename), dpi=300)
plt.close(fig)
def pie(data, filename):
'''
Draw a pie chart
'''
font = {'fontname': 'DejaVu Sans', 'size': 10, 'weight': 'normal'}
fig = plt.figure(figsize=(data['width']/2.54, data['height']/2.54))
chart, texts, autotexts = plt.pie(data['parts'], labels=data['labels'],
colors=data['colors'], autopct='%1.1f%%', explode=data['explode'], startangle=180)
plt.title(data['title'], **font)
for text in texts:
text.set_size('x-small')
for text in autotexts:
text.set_size('xx-small')
plt.savefig("{}".format(filename), dpi=300)
plt.close(fig)
def format_shutter_speed(speed):
speed = speed/1000
if 1/speed >= 4:
shutter_speed = "1/{}".format(int(round(1/speed, 0)))
else:
if speed < 60:
shutter_speed = "{}'".format(round(speed, 3))
else:
speed_minutes = speed//60
speed_seconds = speed%60
shutter_speed = "{}\"{}'".format(speed_minutes, speed_seconds)
return shutter_speed
def title():
'''
Juste the title
'''
text = _("Statistical photo report")
text += "\n"
text += "#"*80
text += "\n\n"
return text
def general(informations, language, colors, temp_dir):
'''
General informations. Take a dict() in argument.
'''
dimensions = []
for dimension in informations['dimensions'].keys():
dimensions.append(float(dimension))
text = _("Pictures informations")
text += "\n"
text += "="*80
text += "\n"
informations_total = informations['total']
start_date = datetime.strptime(sorted(informations['date'])[0], "%Y%m%d").date()
end_date = datetime.strptime(sorted(informations['date'])[-1], "%Y%m%d").date()
most_productive = datetime.strptime(sorted(informations['date'], key=informations['date'].get)[-1], "%Y%m%d").date()
if informations_total > 1:
text += _("The set contains {} pictures, ").format(informations['total'])
elif informations_total == 1:
text += _("The set contains only one picture, ").format(informations['total'])
else:
text += _("The set is empty. ")
if start_date != end_date:
text += _("taken from {} to {}. ").format(
format_date(start_date, locale=language, format="medium"),
format_date(end_date, locale=language, format="medium"))
text += _("The most productive day was {} ({} shots). ").format(
format_date(most_productive, locale=language, format="long"),
informations['date'][sorted(informations['date'], key=informations['date'].get)[-1]])
else:
text += _("taken on {}. ").format(start_date)
text += "\n\n"
if informations_total > 1:
text += _("Pictures sizes are from {1}{0}Mpx to {2}{0}Mpx, mostly {3}{0}Mpx. ").format(
b'\xc2\xa0'.decode(),
sorted(dimensions)[0],
sorted(dimensions)[-1],
sorted(informations['dimensions'], key=informations['dimensions'].get)[-1])
else:
text += _("Picture size is {}{}Mpx. ").format(sorted(dimensions)[0], b'\xc2\xa0'.decode())
text += "\n\n"
if start_date != end_date:
dates = {datetime.strptime(k, "%Y%m%d"):int(v) for k,v in informations['date'].items()}
top_date = sorted(dates, key=dates.get, reverse=True)[0:20]
new_date = {}
for date in top_date:
new_date.update({date: dates[date]})
bar(
{
'width': 16,
'height': 12,
'data': [new_date[date] for date in sorted(new_date)],
'data_labels': [format_date(date, locale=language, format="short") for date in sorted(new_date)],
'title': _("Distribution of shots by date{}").format(_(" : top 20") if len(top_date) == 20 else ""),
'Y_label': _("Number of shots"),
'bar_color': colors[2],
'bar_width': 0.8
},
temp_dir + "/picnb_datetime.pdf")
text += ".. figure:: picnb_datetime.pdf\n\t:width: 16cm\n\n"
return text
def camera_and_lens(informations, language, colors, temp_dir):
'''
Informations about cameras and lenses used.
'''
image1, image2 = None, None
with open("helpers/dictionary.json") as file:
helper = json.load(file)
text = _("Cameras & lenses")
text += "\n"
text += "="*80
text += "\n\n"
top_couple = sorted(informations['cameras+lenses'], key=informations['cameras+lenses'].get)[-1].split(" + ")
top_couple_camera, top_couple_lens = top_couple[0], top_couple[1]
top_lens = sorted(informations['lenses'], key=informations['lenses'].get)[-1]
top_camera = sorted(informations['cameras'], key=informations['cameras'].get)[-1]
text += _("The most commonly used couple is {} with {}. ").format(
(helper['camera'][top_couple_camera]['brand'] + " " + helper['camera'][top_couple_camera]['name']) if top_couple_camera in helper['camera'] else top_couple_camera,
(helper['lens'][top_couple_lens]['brand'] + " " + helper['lens'][top_couple_lens]['name']) if top_couple_lens in helper['lens'] else top_couple_lens,)
text += "\n\n"
if len(informations['lenses']) > 1:
text += _("There are {} lenses, the most used is the {}. ").format(
len(informations['lenses']),
(helper['lens'][top_lens]['brand'] + " " + helper['lens'][top_lens]['name']) if top_lens in helper['lens'] else top_lens)
else:
text += _("The lens is a {}. ").format(
(helper['lens'][top_lens]['brand'] + " " + helper['lens'][top_lens]['name']) if top_lens in helper['lens'] else top_lens)
text += _("The favorite aperture is {}{}, ").format(
_("F"),
sorted(informations['apertures'], key=informations['apertures'].get)[-1])
text += _("and the favorite focal lenght is {}mm. ").format(
sorted(informations['focals'], key=informations['focals'].get)[-1])
text += "\n\n"
if len(informations['cameras']) > 1:
text += _("There are {} cameras, the mose used is the {}. ").format(
len(informations['cameras']),
(helper['camera'][top_camera]['brand'] + " " + helper['camera'][top_camera]['name']) if top_camera in helper['camera'] else top_camera)
else:
text += _("The camera is a {}. ").format(
(helper['camera'][top_camera]['brand'] + " " + helper['camera'][top_camera]['name']) if top_camera in helper['camera'] else top_camera)
text += _(" The favorite ISO is {}. ").format(
sorted(informations['isos'], key=informations['isos'].get)[-1])
text += "\n\n"
if isfile("helpers/cameras/{}.jpg".format(top_camera.replace(" ", "_").replace("/", "_"))):
copyfile(
"helpers/cameras/{}.jpg".format(top_camera.replace(" ", "_").replace("/", "_")),
"{}/{}.jpg".format(temp_dir, top_camera.replace(" ", "_").replace("/", "_")))
image1 = "{}/{}.jpg".format(temp_dir, top_camera.replace(" ", "_").replace("/", "_"))
if isfile("helpers/lenses/{}.jpg".format(top_lens.replace(" ", "_").replace("/", "_"))):
copyfile(
"helpers/lenses/{}.jpg".format(top_lens.replace(" ", "_").replace("/", "_")),
"{}/{}.jpg".format(temp_dir, top_lens.replace(" ", "_").replace("/", "_")))
image2 = "{}/{}.jpg".format(temp_dir, top_lens.replace(" ", "_").replace("/", "_"))
if image1 and image2:
run(["montage", image1, image2, "-geometry", "+10+0", "-quality", "94", "{}/top_camera_lens.jpg".format(temp_dir)], check=True)
text += ".. image:: {}.jpg\n\t:height: 5cm\n\n".format("top_camera_lens")
if len(informations['cameras']) > 1:
pie(
{
'width': 17,
'height': 8,
'parts': [informations['cameras'][camera] for camera in sorted(informations['cameras'], key=informations['cameras'].get)],
'labels': [((helper['camera'][camera]['brand'] + " " + helper['camera'][camera]['name']) if camera in helper['camera'] else camera) for camera in sorted(informations['cameras'], key=informations['cameras'].get)],
'colors': colors[0:len(list(informations['cameras'].keys()))],
'explode': [0.02]*len(list(informations['cameras'].keys())),
'title': _("Cameras")
},
temp_dir + "/cameras_parts.pdf")
text += "\n\n"
text += ".. figure:: cameras_parts.pdf\n\t:width: 17cm\n\n"
if len(informations['lenses']) > 1:
pie(
{
'width': 17,
'height': 8,
'parts': [informations['lenses'][lens] for lens in sorted(informations['lenses'], key=informations['lenses'].get)],
'labels': [((helper['lens'][lens]['brand'] + " " + helper['lens'][lens]['name']) if lens in helper['lens'] else lens) for lens in sorted(informations['lenses'], key=informations['lenses'].get)],
'colors': colors[0:len(list(informations['lenses'].keys()))],
'explode': [0.02]*len(list(informations['lenses'].keys())),
'title': _("Lenses")
},
temp_dir + "/lenses_parts.pdf")
text += "\n\n"
text += ".. figure:: lenses_parts.pdf\n\t:width: 17cm\n\n"
return text
def focal_aperture(informations, language, colors, temp_dir):
text = _("Focal lengths & apertures")
text += "\n" + "="*80 + "\n"
text += "\n\n"
text += _("Focal lenghts")
text += "\n" + "-"*80 + "\n"
if len(informations['focals']) > 1:
focals = {float(k): int(v) for k,v in informations['focals'].items()}
top_focals = sorted(focals, key=focals.get, reverse=True)[0:20]
new_focals = {}
for focal in top_focals:
new_focals.update({focal: focals[focal]})
bar(
{
'width': 17,
'height': 8,
'data': [new_focals[focal] for focal in sorted(new_focals)],
'bar_width': 0.8,
'bar_color': colors[0],
'data_labels': ["{} mm".format(int(focal) if int(focal) == float(focal) else float(focal)) for focal in sorted(new_focals)],
'Y_label': _("Number of shots"),
'title': _("Focals{}").format(_(" : top 20") if len(top_focals) == 20 else "")
},
temp_dir + "/focal_lenghts.pdf")
text += ".. figure:: focal_lenghts.pdf\n\t:width: 17cm\n\n"
else:
text += _("Only one focal lenght is used : {}mm. ").format(float(sorted(informations['focals'], key=informations['focals'].get)[0]))
text += "\n\n"
text += _("Apertures")
text += "\n" + "-"*80 + "\n"
if len(informations['apertures']) > 1:
apertures = {float(k):int(v) for k,v in informations['apertures'].items()}
top_apertures = sorted(apertures, key=apertures.get, reverse=True)[0:20]
new_apertures = {}
for aperture in top_apertures:
new_apertures.update({aperture: apertures[aperture]})
bar(
{
'width': 17,
'height': 8,
'data': [new_apertures[aperture] for aperture in sorted(new_apertures)],
'bar_width': 0.8,
'bar_color': colors[5],
'data_labels': ["{}{}".format(_("F"), int(aperture) if int(aperture) == float(aperture) else float(aperture)) for aperture in sorted(new_apertures)],
'Y_label': _("Number of shots"),
'title': _("Apertures{}").format(_(" : top 20") if len(top_apertures) == 20 else "")
},
temp_dir + "/apertures.pdf")
text += ".. figure:: apertures.pdf\n\t:width: 17cm\n\n"
else:
text += _("Only one aperture is used : {}{}. ").format(
_("F"),
float(sorted(informations['apertures'], key=informations['apertures'].get)[0]))
text += "\n\n"
return text
def iso_shutter(informations, language, colors, temp_dir):
text = _("ISO sensitivities & shutter speeds")
text += "\n" + "="*80 + "\n"
text += _("ISO sensitivities")
text += "\n" + "-"*80 + "\n"
if len(informations['isos']) > 1:
isos = {float(k):int(v) for k,v in informations['isos'].items()}
top_isos = sorted(isos, key=isos.get, reverse=True)[0:20]
new_isos = {}
for iso in top_isos:
new_isos.update({iso: isos[iso]})
bar(
{
'width': 17,
'height': 8,
'data': [new_isos[iso] for iso in sorted(new_isos)],
'bar_width': 0.8,
'bar_color': colors[3],
'data_labels': ["ISO {}".format(int(iso) if int(iso) == float(iso) else float(iso)) for iso in sorted(new_isos)],
'Y_label': _("Number of shots"),
'title': _("ISO sensibilities{}").format(_(" : top 20") if len(top_isos) == 20 else "")
},
temp_dir + "/isos.pdf")
text += ".. figure:: isos.pdf\n\t:width: 17cm\n\n"
else:
text += _("All shots at the same sensitivity : ISO {} ").format(sorted(informations['isos'], key=informations['isos'].get)[0])
text += "\n\n"
text += _("Shutter speeds")
text += "\n" + "-"*80 + "\n"
if len(informations['shutter_speeds']) > 1:
shutter_speeds = {float(k):int(v) for k,v in informations['shutter_speeds'].items()}
top_shutter_speeds = sorted(shutter_speeds, key=shutter_speeds.get, reverse=True)[0:20]
new_shutter_speeds = {}
for shutter_speed in top_shutter_speeds:
new_shutter_speeds.update({shutter_speed: shutter_speeds[shutter_speed]})
bar(
{
'width': 17,
'height': 8,
'data': [new_shutter_speeds[shutter_speed] for shutter_speed in sorted(new_shutter_speeds)],
'bar_width': 0.8,
'bar_color': colors[7],
'data_labels': [format_shutter_speed(shutter_speed) for shutter_speed in sorted(new_shutter_speeds)],
'Y_label': _("Number of shots"),
'title': _("Shutter speeds{}").format(_(" : top 20") if len(top_shutter_speeds) == 20 else "")
},
temp_dir + "/speeds.pdf")
text += ".. figure:: speeds.pdf\n\t:width: 17cm\n\n"
else:
text += _("All shots at the same speed : {}. ").format(format_shutter_speed(sorted(informations['shutter_speeds'], key=informations['shutter_speeds'].get)[0]))
return text
def footer():
return ".. footer::\n\n\t###Page###\n\n"
def page_break():
return ".. raw:: pdf\n\n\tPageBreak\n\n"
def create(informations, language, output_file):
with open("helpers/dictionary.json") as file:
helper = json.load(file)
colors = [
"#b58900", # yellow
"#cb4b16", # orange
"#dc322f", # red
"#d33682", # magenta
"#6c71c4", # violet
"#268bd2", # blue
"#2aa198", # cyan
"#859900" # green
]
i18n(language)
temp_dir = mkdtemp()
document = footer()
document += title()
document += general(informations, language, colors, temp_dir)
document += page_break()
document += camera_and_lens(informations, language, colors, temp_dir)
document += page_break()
document += focal_aperture(informations, language, colors, temp_dir)
document += page_break()
document += iso_shutter(informations, language, colors, temp_dir)
with open(temp_dir + "/report.rst", "w") as rst_file:
rst_file.write(document)
try:
run(["rst2pdf", temp_dir + "/report.rst", "-s", "rst2pdf_stylsheet.style", "-o", output_file], check=True)
rmtree(temp_dir)
return True
except CalledProcessError:
return False
if __name__ == '__main__':
import argparse
import json
parser = argparse.ArgumentParser()
parser.add_argument("infile", help="JSON output from a reader")
parser.add_argument("--lang", help="i18n option", default="en")
parser.add_argument("--out", "-o", help="outfile", default="./report.pdf")
args = parser.parse_args()
with open(args.infile, "r") as json_data:
data = json.load(json_data)
create(data, args.lang, args.out)

5
requirements.txt Normal file
View File

@ -0,0 +1,5 @@
Flask
numpy
matplotlib
babel
piexif

16
rst2pdf_stylsheet.style Normal file
View File

@ -0,0 +1,16 @@
{"fontsAlias": {
"stdFont": "DejaVu Sans",
"stdBold": "DejaVu Sans",
"stdItalic": "DejaVu Sans",
"stdBoldItalic": "DejaVu Sans",
"sdtMono": "DejaVu Sans Mono"
},
"pageSetup": {
"size": "A4",
"width": null,
"height": null,
"margin-top": "1.2cm",
"margin-bottom": "1cm",
"margin-left": "1.5cm",
"margin-right": "1.5cm"
}}

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
}