Compare commits
17 Commits
Author | SHA1 | Date |
---|---|---|
|
d54ccd64f6 | |
|
836792429f | |
|
b7435c4eac | |
|
723f549f73 | |
|
47b9aa57ae | |
|
9e32052ce3 | |
|
ed58383ea0 | |
|
f6816c6c01 | |
|
b7f0cafe98 | |
|
573a63998e | |
|
01b47c8c40 | |
|
52768f64db | |
|
cdfa408206 | |
|
eee3c71f6a | |
|
1eb2a2c6e0 | |
|
58e282fd87 | |
|
a1ba0663b6 |
|
@ -1,20 +1,139 @@
|
||||||
|
# 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/
|
||||||
|
pip-wheel-metadata/
|
||||||
|
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/
|
||||||
|
|
||||||
|
# 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
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.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/
|
||||||
|
|
||||||
# Compiled python modules.
|
# Compiled python modules.
|
||||||
*.pyc
|
*.pyc
|
||||||
|
|
||||||
/build/
|
# Other
|
||||||
/.coverage
|
|
||||||
/diagnostics.lua
|
/diagnostics.lua
|
||||||
docs/_build
|
|
||||||
docs/Ordigi_data_scheme.odg
|
docs/Ordigi_data_scheme.odg
|
||||||
|
|
||||||
# Setuptools distribution folder.
|
|
||||||
/dist/
|
|
||||||
|
|
||||||
# Python egg metadata, regenerated from source files by setuptools.
|
|
||||||
/*.egg-info
|
|
||||||
|
|
||||||
/env/
|
|
||||||
/htmlcov
|
|
||||||
/ressources
|
/ressources
|
||||||
/Session.vim
|
/Session.vim
|
||||||
/tags
|
/tags
|
||||||
|
|
119
docs/ordigi.mm
119
docs/ordigi.mm
|
@ -1,119 +0,0 @@
|
||||||
<map version="freeplane 1.7.0">
|
|
||||||
<!--To view this file, download free mind mapping software Freeplane from http://freeplane.sourceforge.net -->
|
|
||||||
<node TEXT="elodie" FOLDED="false" ID="ID_577640973" CREATED="1624709002278" MODIFIED="1624709019473" STYLE="oval"><hook NAME="MapStyle">
|
|
||||||
<conditional_styles>
|
|
||||||
<conditional_style ACTIVE="true" LOCALIZED_STYLE_REF="styles.connection" LAST="false">
|
|
||||||
<node_periodic_level_condition PERIOD="2" REMAINDER="1"/>
|
|
||||||
</conditional_style>
|
|
||||||
<conditional_style ACTIVE="true" LOCALIZED_STYLE_REF="styles.topic" LAST="false">
|
|
||||||
<node_level_condition VALUE="2" MATCH_CASE="false" MATCH_APPROXIMATELY="false" COMPARATION_RESULT="0" SUCCEED="true"/>
|
|
||||||
</conditional_style>
|
|
||||||
<conditional_style ACTIVE="true" LOCALIZED_STYLE_REF="styles.subtopic" LAST="false">
|
|
||||||
<node_level_condition VALUE="4" MATCH_CASE="false" MATCH_APPROXIMATELY="false" COMPARATION_RESULT="0" SUCCEED="true"/>
|
|
||||||
</conditional_style>
|
|
||||||
<conditional_style ACTIVE="true" LOCALIZED_STYLE_REF="styles.subsubtopic" LAST="false">
|
|
||||||
<node_level_condition VALUE="6" MATCH_CASE="false" MATCH_APPROXIMATELY="false" COMPARATION_RESULT="0" SUCCEED="true"/>
|
|
||||||
</conditional_style>
|
|
||||||
</conditional_styles>
|
|
||||||
<properties edgeColorConfiguration="#808080ff,#ff0000ff,#0000ffff,#00ff00ff,#ff00ffff,#00ffffff,#7c0000ff,#00007cff,#007c00ff,#7c007cff,#007c7cff,#7c7c00ff" fit_to_viewport="false" show_note_icons="true"/>
|
|
||||||
|
|
||||||
<map_styles>
|
|
||||||
<stylenode LOCALIZED_TEXT="styles.root_node" STYLE="oval" UNIFORM_SHAPE="true" VGAP_QUANTITY="24.0 pt">
|
|
||||||
<font SIZE="24"/>
|
|
||||||
<stylenode LOCALIZED_TEXT="styles.predefined" POSITION="right" STYLE="bubble">
|
|
||||||
<stylenode LOCALIZED_TEXT="default" ICON_SIZE="12.0 pt" COLOR="#000000" STYLE="fork">
|
|
||||||
<font NAME="Arial" SIZE="10" BOLD="false" ITALIC="false"/>
|
|
||||||
</stylenode>
|
|
||||||
<stylenode LOCALIZED_TEXT="defaultstyle.details"/>
|
|
||||||
<stylenode LOCALIZED_TEXT="defaultstyle.attributes">
|
|
||||||
<font SIZE="9"/>
|
|
||||||
</stylenode>
|
|
||||||
<stylenode LOCALIZED_TEXT="defaultstyle.note" COLOR="#000000" BACKGROUND_COLOR="#ffffff" TEXT_ALIGN="LEFT"/>
|
|
||||||
<stylenode LOCALIZED_TEXT="defaultstyle.floating">
|
|
||||||
<edge STYLE="hide_edge"/>
|
|
||||||
<cloud COLOR="#f0f0f0" SHAPE="ROUND_RECT"/>
|
|
||||||
</stylenode>
|
|
||||||
</stylenode>
|
|
||||||
<stylenode LOCALIZED_TEXT="styles.user-defined" POSITION="right" STYLE="bubble">
|
|
||||||
<stylenode LOCALIZED_TEXT="styles.topic" COLOR="#18898b" STYLE="fork">
|
|
||||||
<font NAME="Liberation Sans" SIZE="10" BOLD="true"/>
|
|
||||||
</stylenode>
|
|
||||||
<stylenode LOCALIZED_TEXT="styles.subtopic" COLOR="#cc3300" STYLE="fork">
|
|
||||||
<font NAME="Liberation Sans" SIZE="10" BOLD="true"/>
|
|
||||||
</stylenode>
|
|
||||||
<stylenode LOCALIZED_TEXT="styles.subsubtopic" COLOR="#669900">
|
|
||||||
<font NAME="Liberation Sans" SIZE="10" BOLD="true"/>
|
|
||||||
</stylenode>
|
|
||||||
<stylenode LOCALIZED_TEXT="styles.connection" COLOR="#606060" STYLE="fork">
|
|
||||||
<font NAME="Arial" SIZE="8" BOLD="false"/>
|
|
||||||
</stylenode>
|
|
||||||
</stylenode>
|
|
||||||
<stylenode LOCALIZED_TEXT="styles.AutomaticLayout" POSITION="right" STYLE="bubble">
|
|
||||||
<stylenode LOCALIZED_TEXT="AutomaticLayout.level.root" COLOR="#000000" STYLE="oval">
|
|
||||||
<font SIZE="18"/>
|
|
||||||
</stylenode>
|
|
||||||
<stylenode LOCALIZED_TEXT="AutomaticLayout.level,1" COLOR="#0033ff">
|
|
||||||
<font SIZE="16"/>
|
|
||||||
</stylenode>
|
|
||||||
<stylenode LOCALIZED_TEXT="AutomaticLayout.level,2" COLOR="#00b439">
|
|
||||||
<font SIZE="14"/>
|
|
||||||
</stylenode>
|
|
||||||
<stylenode LOCALIZED_TEXT="AutomaticLayout.level,3" COLOR="#990000">
|
|
||||||
<font SIZE="12"/>
|
|
||||||
</stylenode>
|
|
||||||
<stylenode LOCALIZED_TEXT="AutomaticLayout.level,4" COLOR="#111111">
|
|
||||||
<font SIZE="10"/>
|
|
||||||
</stylenode>
|
|
||||||
</stylenode>
|
|
||||||
</stylenode>
|
|
||||||
</map_styles>
|
|
||||||
</hook>
|
|
||||||
<node TEXT="import" POSITION="right" ID="ID_1958811617" CREATED="1624709031603" MODIFIED="1624710428698"><richcontent TYPE="NOTE">
|
|
||||||
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<p>
|
|
||||||
Import from external source
|
|
||||||
</p>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
</richcontent>
|
|
||||||
<node TEXT="--update" ID="ID_1408411362" CREATED="1624710635676" MODIFIED="1624710643751"/>
|
|
||||||
</node>
|
|
||||||
<node TEXT="update" POSITION="right" ID="ID_200299843" CREATED="1624709041259" MODIFIED="1624710451112"><richcontent TYPE="NOTE">
|
|
||||||
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<p>
|
|
||||||
Update metadata
|
|
||||||
</p>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
</richcontent>
|
|
||||||
</node>
|
|
||||||
<node TEXT="sort" FOLDED="true" POSITION="right" ID="ID_474160274" CREATED="1624709213958" MODIFIED="1624710465196"><richcontent TYPE="NOTE">
|
|
||||||
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<p>
|
|
||||||
Sort photo
|
|
||||||
</p>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
</richcontent>
|
|
||||||
<node TEXT="sort files" ID="ID_1215066925" CREATED="1624709364728" MODIFIED="1624709367203"/>
|
|
||||||
</node>
|
|
||||||
</node>
|
|
||||||
</map>
|
|
241
notes.md
241
notes.md
|
@ -1,241 +0,0 @@
|
||||||
|
|
||||||
# Name ideas
|
|
||||||
dozo
|
|
||||||
fog
|
|
||||||
mtool
|
|
||||||
ordigi
|
|
||||||
|
|
||||||
# Geocoders
|
|
||||||
- Pelias
|
|
||||||
- Photon
|
|
||||||
- Nominatium
|
|
||||||
|
|
||||||
# TEST
|
|
||||||
|
|
||||||
def get_exif(filename):
|
|
||||||
image = Image.open(filename)
|
|
||||||
image.verify()
|
|
||||||
return image._getexif()
|
|
||||||
|
|
||||||
def get_geotagging(exif):
|
|
||||||
if not exif:
|
|
||||||
raise ValueError("No EXIF metadata found")
|
|
||||||
|
|
||||||
geotagging = {}
|
|
||||||
for (idx, tag) in TAGS.items():
|
|
||||||
if tag == 'GPSInfo':
|
|
||||||
if idx not in exif:
|
|
||||||
raise ValueError("No EXIF geotagging found")
|
|
||||||
|
|
||||||
for (key, val) in GPSTAGS.items():
|
|
||||||
if key in exif[idx]:
|
|
||||||
geotagging[val] = exif[idx][key]
|
|
||||||
|
|
||||||
return geotagging
|
|
||||||
get_geotagging(exif)
|
|
||||||
from PIL.ExifTags import TAGS
|
|
||||||
|
|
||||||
def get_labeled_exif(exif):
|
|
||||||
labeled = {}
|
|
||||||
for (key, val) in exif.items():
|
|
||||||
labeled[TAGS.get(key)] = val
|
|
||||||
|
|
||||||
return labeled
|
|
||||||
get_geotagging(exif)
|
|
||||||
from PIL.ExifTags import GPSTAGS
|
|
||||||
get_geotagging(exif)
|
|
||||||
geotags = get_geotagging(exif)
|
|
||||||
get_location(geotags)
|
|
||||||
|
|
||||||
def get_decimal_from_dms(dms, ref):
|
|
||||||
|
|
||||||
degrees = dms[0][0] / dms[0][1]
|
|
||||||
minutes = dms[1][0] / dms[1][1] / 60.0
|
|
||||||
seconds = dms[2][0] / dms[2][1] / 3600.0
|
|
||||||
|
|
||||||
if ref in ['S', 'W']:
|
|
||||||
degrees = -degrees
|
|
||||||
minutes = -minutes
|
|
||||||
seconds = -seconds
|
|
||||||
|
|
||||||
return round(degrees + minutes + seconds, 5)
|
|
||||||
|
|
||||||
def get_coordinates(geotags):
|
|
||||||
lat = get_decimal_from_dms(geotags['GPSLatitude'], geotags['GPSLatitudeRef'])
|
|
||||||
|
|
||||||
lon = get_decimal_from_dms(geotags['GPSLongitude'], geotags['GPSLongitudeRef'])
|
|
||||||
|
|
||||||
return (lat,lon)
|
|
||||||
|
|
||||||
def get_geotagging(exif):
|
|
||||||
if not exif:
|
|
||||||
raise ValueError("No EXIF metadata found")
|
|
||||||
|
|
||||||
geotagging = {}
|
|
||||||
for (idx, tag) in TAGS.items():
|
|
||||||
if tag == 'GPSInfo':
|
|
||||||
if idx not in exif:
|
|
||||||
raise ValueError("No EXIF geotagging found")
|
|
||||||
|
|
||||||
for (key, val) in GPSTAGS.items():
|
|
||||||
if key in exif[idx]:
|
|
||||||
geotagging[val] = exif[idx][key]
|
|
||||||
|
|
||||||
return geotagging
|
|
||||||
|
|
||||||
def get_decimal_from_dms(dms, ref):
|
|
||||||
|
|
||||||
degrees = dms[0]
|
|
||||||
minutes = dms[1] / 60.0
|
|
||||||
seconds = dms[2] / 3600.0
|
|
||||||
|
|
||||||
if ref in ['S', 'W']:
|
|
||||||
degrees = -degrees
|
|
||||||
minutes = -minutes
|
|
||||||
seconds = -seconds
|
|
||||||
|
|
||||||
return round(degrees + minutes + seconds, 5)
|
|
||||||
headers = {}
|
|
||||||
params = {
|
|
||||||
'apiKey': os.environ['API_KEY'],
|
|
||||||
'at': "%s,%s" % coords,
|
|
||||||
'lang': 'en-US',
|
|
||||||
'limit': 1,
|
|
||||||
}
|
|
||||||
78/41: headers = {}
|
|
||||||
78/42:
|
|
||||||
params = {
|
|
||||||
'apiKey': os.environ['API_KEY'],
|
|
||||||
'at': "%s,%s" % coords,
|
|
||||||
'lang': 'en-US',
|
|
||||||
'limit': 1,
|
|
||||||
}
|
|
||||||
78/43:
|
|
||||||
params = {
|
|
||||||
'apiKey': os.environ['API_KEY'],
|
|
||||||
'at': "%s,%s" % coords,
|
|
||||||
'lang': 'en-US',
|
|
||||||
'limit': 1,
|
|
||||||
}
|
|
||||||
78/44: API_KEY=m5aGo8xGe4LLhxeKZYpHr2MPXGN2aDhe
|
|
||||||
78/45: API_KEY='m5aGo8xGe4LLhxeKZYpHr2MPXGN2aDhe'
|
|
||||||
78/46:
|
|
||||||
params = {
|
|
||||||
'apiKey': os.environ['API_KEY'],
|
|
||||||
'at': "%s,%s" % coords,
|
|
||||||
'lang': 'en-US',
|
|
||||||
'limit': 1,
|
|
||||||
}
|
|
||||||
78/47: API_KEY='m5aGo8xGe4LLhxeKZYpHr2MPXGN2aDhe'
|
|
||||||
78/48:
|
|
||||||
params = {
|
|
||||||
'apiKey': os.environ['API_KEY'],
|
|
||||||
'at': "%s,%s" % coords,
|
|
||||||
'lang': 'en-US',
|
|
||||||
'limit': 1,
|
|
||||||
}
|
|
||||||
78/49:
|
|
||||||
params = {
|
|
||||||
'apiKey': os.environ['m5aGo8xGe4LLhxeKZYpHr2MPXGN2aDhe'],
|
|
||||||
'at': "%s,%s" % coords,
|
|
||||||
'lang': 'en-US',
|
|
||||||
'limit': 1,
|
|
||||||
}
|
|
||||||
78/50: %load_ext autotime
|
|
||||||
78/51:
|
|
||||||
import pandas as pd
|
|
||||||
import geopandas as gpd
|
|
||||||
import geopy
|
|
||||||
from geopy.geocoders import Nominatim
|
|
||||||
from geopy.extra.rate_limiter import RateLimiterimport matplotlib.pyplot as plt
|
|
||||||
import plotly_express as pximport tqdm
|
|
||||||
from tqdm._tqdm_notebook import tqdm_notebook
|
|
||||||
78/52:
|
|
||||||
import pandas as pd
|
|
||||||
import geopandas as gpd
|
|
||||||
import geopy
|
|
||||||
from geopy.geocoders import Nominatim
|
|
||||||
from geopy.extra.rate_limiter import RateLimiterimport matplotlib.pyplot as plt
|
|
||||||
import plotly_express as px
|
|
||||||
import pandas as pd
|
|
||||||
import geopandas as gpd
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
filename='2021-02-24_09-33-29-20210305_081001_01.mp4'
|
|
||||||
def get_exif(filename):
|
|
||||||
image = Image.open(filename)
|
|
||||||
image.verify()
|
|
||||||
return image._getexif()
|
|
||||||
exif=get_exif(filename)
|
|
||||||
|
|
||||||
from PIL.ExifTags import TAGS
|
|
||||||
from PIL.ExifTags import GPSTAGS
|
|
||||||
def get_geotagging(exif):
|
|
||||||
if not exif:
|
|
||||||
raise ValueError("No EXIF metadata found")
|
|
||||||
|
|
||||||
geotagging = {}
|
|
||||||
for (idx, tag) in TAGS.items():
|
|
||||||
if tag == 'GPSInfo':
|
|
||||||
if idx not in exif:
|
|
||||||
raise ValueError("No EXIF geotagging found")
|
|
||||||
|
|
||||||
for (key, val) in GPSTAGS.items():
|
|
||||||
if key in exif[idx]:
|
|
||||||
geotagging[val] = exif[idx][key]
|
|
||||||
|
|
||||||
return geotagging
|
|
||||||
geotags = get_geotagging(exif)
|
|
||||||
import os
|
|
||||||
import requests
|
|
||||||
|
|
||||||
def get_location(geotags):
|
|
||||||
coords = get_coordinates(geotags)
|
|
||||||
|
|
||||||
uri = 'https://revgeocode.search.hereapi.com/v1/revgeocode'
|
|
||||||
headers = {}
|
|
||||||
params = {
|
|
||||||
'apiKey': os.environ['API_KEY'],
|
|
||||||
'at': "%s,%s" % coords,
|
|
||||||
'lang': 'en-US',
|
|
||||||
'limit': 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
response = requests.get(uri, headers=headers, params=params)
|
|
||||||
try:
|
|
||||||
response.raise_for_status()
|
|
||||||
return response.json()
|
|
||||||
|
|
||||||
except requests.exceptions.HTTPError as e:
|
|
||||||
print(str(e))
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def get_coordinates(geotags):
|
|
||||||
lat = get_decimal_from_dms(geotags['GPSLatitude'], geotags['GPSLatitudeRef'])
|
|
||||||
|
|
||||||
lon = get_decimal_from_dms(geotags['GPSLongitude'], geotags['GPSLongitudeRef'])
|
|
||||||
|
|
||||||
return (lat,lon)
|
|
||||||
coords = get_coordinates(geotags)
|
|
||||||
import geopy
|
|
||||||
from geopy.geocoders import Nominatim
|
|
||||||
locator = Nominatim(user_agent='myGeocoder')
|
|
||||||
# coordinates ='53.480837, -2.244914'
|
|
||||||
lat='45.58339'
|
|
||||||
lon='4.79823'
|
|
||||||
coords = lat + ',' + lon
|
|
||||||
locator.reverse(coords)
|
|
||||||
location =locator.reverse(coords)
|
|
||||||
location.address.split(',')
|
|
||||||
city=location.address.split(',')[1].strip()
|
|
||||||
country=location.address.split(',')[7].strip()
|
|
||||||
location.raw
|
|
||||||
rint
|
|
||||||
country=location.raw['address']['country']
|
|
||||||
city=location.raw['address']['village']
|
|
|
@ -12,11 +12,26 @@ from ordigi.geolocation import GeoLocation
|
||||||
from ordigi import utils
|
from ordigi import utils
|
||||||
|
|
||||||
_logger_options = [
|
_logger_options = [
|
||||||
|
click.option(
|
||||||
|
'--quiet',
|
||||||
|
'-q',
|
||||||
|
default=False,
|
||||||
|
is_flag=True,
|
||||||
|
help='Log level set to ERROR',
|
||||||
|
),
|
||||||
click.option(
|
click.option(
|
||||||
'--verbose',
|
'--verbose',
|
||||||
'-v',
|
'-v',
|
||||||
default='WARNING',
|
default=False,
|
||||||
help='Log level [WARNING,INFO,DEBUG,NOTSET]',
|
is_flag=True,
|
||||||
|
help='Log level set to INFO',
|
||||||
|
),
|
||||||
|
click.option(
|
||||||
|
'--debug',
|
||||||
|
'-d',
|
||||||
|
default=False,
|
||||||
|
is_flag=True,
|
||||||
|
help='Log level set to DEBUG',
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -168,7 +183,7 @@ def _check(**kwargs):
|
||||||
"""
|
"""
|
||||||
root = Path(kwargs['path']).expanduser().absolute()
|
root = Path(kwargs['path']).expanduser().absolute()
|
||||||
|
|
||||||
log_level = log.get_level(kwargs['verbose'])
|
log_level = log.get_level(kwargs['quiet'], kwargs['verbose'], kwargs['debug'])
|
||||||
log.console(LOG, level=log_level)
|
log.console(LOG, level=log_level)
|
||||||
|
|
||||||
collection = Collection(root)
|
collection = Collection(root)
|
||||||
|
@ -191,7 +206,7 @@ def _check(**kwargs):
|
||||||
@add_options(_filter_options)
|
@add_options(_filter_options)
|
||||||
@click.option(
|
@click.option(
|
||||||
'--dedup-regex',
|
'--dedup-regex',
|
||||||
'-d',
|
'-D',
|
||||||
default=None,
|
default=None,
|
||||||
multiple=True,
|
multiple=True,
|
||||||
help='Regex to match duplicate strings parts',
|
help='Regex to match duplicate strings parts',
|
||||||
|
@ -218,7 +233,7 @@ def _clean(**kwargs):
|
||||||
"""Clean media collection"""
|
"""Clean media collection"""
|
||||||
|
|
||||||
folders = kwargs['folders']
|
folders = kwargs['folders']
|
||||||
log_level = log.get_level(kwargs['verbose'])
|
log_level = log.get_level(kwargs['quiet'], kwargs['verbose'], kwargs['debug'])
|
||||||
log.console(LOG, level=log_level)
|
log.console(LOG, level=log_level)
|
||||||
|
|
||||||
subdirs = kwargs['subdirs']
|
subdirs = kwargs['subdirs']
|
||||||
|
@ -268,7 +283,7 @@ def _clean(**kwargs):
|
||||||
def _clone(**kwargs):
|
def _clone(**kwargs):
|
||||||
"""Clone media collection to another location"""
|
"""Clone media collection to another location"""
|
||||||
|
|
||||||
log_level = log.get_level(kwargs['verbose'])
|
log_level = log.get_level(kwargs['quiet'], kwargs['verbose'], kwargs['debug'])
|
||||||
log.console(LOG, level=log_level)
|
log.console(LOG, level=log_level)
|
||||||
|
|
||||||
src_path = Path(kwargs['src']).expanduser().absolute()
|
src_path = Path(kwargs['src']).expanduser().absolute()
|
||||||
|
@ -321,7 +336,7 @@ def _compare(**kwargs):
|
||||||
subdirs = kwargs['subdirs']
|
subdirs = kwargs['subdirs']
|
||||||
root = kwargs['collection']
|
root = kwargs['collection']
|
||||||
|
|
||||||
log_level = log.get_level(kwargs['verbose'])
|
log_level = log.get_level(kwargs['quiet'], kwargs['verbose'], kwargs['debug'])
|
||||||
log.console(LOG, level=log_level)
|
log.console(LOG, level=log_level)
|
||||||
paths, root = _get_paths(subdirs, root)
|
paths, root = _get_paths(subdirs, root)
|
||||||
|
|
||||||
|
@ -358,16 +373,25 @@ def _compare(**kwargs):
|
||||||
multiple=True,
|
multiple=True,
|
||||||
help="Select exif tags groups to edit",
|
help="Select exif tags groups to edit",
|
||||||
)
|
)
|
||||||
|
@click.option(
|
||||||
|
'--overwrite',
|
||||||
|
'-O',
|
||||||
|
default=False,
|
||||||
|
is_flag=True,
|
||||||
|
help="Overwrite db and exif value by key value",
|
||||||
|
)
|
||||||
@click.argument('subdirs', required=False, nargs=-1, type=click.Path())
|
@click.argument('subdirs', required=False, nargs=-1, type=click.Path())
|
||||||
@click.argument('path', required=True, nargs=1, type=click.Path())
|
@click.argument('path', required=True, nargs=1, type=click.Path())
|
||||||
def _edit(**kwargs):
|
def _edit(**kwargs):
|
||||||
"""Edit EXIF metadata in files or directories"""
|
"""Edit EXIF metadata in files or directories"""
|
||||||
|
|
||||||
log_level = log.get_level(kwargs['verbose'])
|
log_level = log.get_level(kwargs['quiet'], kwargs['verbose'], kwargs['debug'])
|
||||||
log.console(LOG, level=log_level)
|
log.console(LOG, level=log_level)
|
||||||
|
|
||||||
paths, root = _get_paths(kwargs['subdirs'], kwargs['path'])
|
paths, root = _get_paths(kwargs['subdirs'], kwargs['path'])
|
||||||
|
|
||||||
|
overwrite = kwargs['overwrite']
|
||||||
|
|
||||||
collection = Collection(
|
collection = Collection(
|
||||||
root,
|
root,
|
||||||
{
|
{
|
||||||
|
@ -384,13 +408,11 @@ def _edit(**kwargs):
|
||||||
'camera_make',
|
'camera_make',
|
||||||
'camera_model',
|
'camera_model',
|
||||||
'city',
|
'city',
|
||||||
'coordinates',
|
|
||||||
'country',
|
'country',
|
||||||
# 'date_created',
|
# 'date_created',
|
||||||
'date_media',
|
'date_media',
|
||||||
# 'date_modified',
|
# 'date_modified',
|
||||||
'date_original',
|
'date_original',
|
||||||
'default',
|
|
||||||
'latitude',
|
'latitude',
|
||||||
'location',
|
'location',
|
||||||
'longitude',
|
'longitude',
|
||||||
|
@ -405,6 +427,9 @@ def _edit(**kwargs):
|
||||||
keys = set(editable_keys)
|
keys = set(editable_keys)
|
||||||
else:
|
else:
|
||||||
keys = set(kwargs['key'])
|
keys = set(kwargs['key'])
|
||||||
|
if 'coordinates' in keys:
|
||||||
|
keys.remove('coordinates')
|
||||||
|
keys.update(['latitude', 'longitude'])
|
||||||
|
|
||||||
location = False
|
location = False
|
||||||
for key in keys:
|
for key in keys:
|
||||||
|
@ -412,10 +437,6 @@ def _edit(**kwargs):
|
||||||
LOG.error(f"key '{key}' is not valid")
|
LOG.error(f"key '{key}' is not valid")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if key == 'coordinates':
|
|
||||||
keys.remove('coordinates')
|
|
||||||
keys.update(['latitude', 'longitude'])
|
|
||||||
|
|
||||||
if key in (
|
if key in (
|
||||||
'city',
|
'city',
|
||||||
'latitude',
|
'latitude',
|
||||||
|
@ -431,7 +452,7 @@ def _edit(**kwargs):
|
||||||
else:
|
else:
|
||||||
loc = None
|
loc = None
|
||||||
|
|
||||||
summary = collection.edit_metadata(paths, keys, loc, overwrite=True)
|
summary = collection.edit_metadata(paths, keys, loc, overwrite)
|
||||||
|
|
||||||
if log_level < 30:
|
if log_level < 30:
|
||||||
summary.print()
|
summary.print()
|
||||||
|
@ -448,7 +469,7 @@ def _init(**kwargs):
|
||||||
Init media collection database.
|
Init media collection database.
|
||||||
"""
|
"""
|
||||||
root = Path(kwargs['path']).expanduser().absolute()
|
root = Path(kwargs['path']).expanduser().absolute()
|
||||||
log_level = log.get_level(kwargs['verbose'])
|
log_level = log.get_level(kwargs['quiet'], kwargs['verbose'], kwargs['debug'])
|
||||||
log.console(LOG, level=log_level)
|
log.console(LOG, level=log_level)
|
||||||
|
|
||||||
collection = Collection(root)
|
collection = Collection(root)
|
||||||
|
@ -485,7 +506,7 @@ def _import(**kwargs):
|
||||||
"""Sort files or directories by reading their EXIF and organizing them
|
"""Sort files or directories by reading their EXIF and organizing them
|
||||||
according to ordigi.conf preferences.
|
according to ordigi.conf preferences.
|
||||||
"""
|
"""
|
||||||
log_level = log.get_level(kwargs['verbose'])
|
log_level = log.get_level(kwargs['quiet'], kwargs['verbose'], kwargs['debug'])
|
||||||
log.console(LOG, level=log_level)
|
log.console(LOG, level=log_level)
|
||||||
|
|
||||||
src_paths, root = _get_paths(kwargs['src'], kwargs['dest'])
|
src_paths, root = _get_paths(kwargs['src'], kwargs['dest'])
|
||||||
|
@ -541,7 +562,7 @@ def _sort(**kwargs):
|
||||||
"""Sort files or directories by reading their EXIF and organizing them
|
"""Sort files or directories by reading their EXIF and organizing them
|
||||||
according to ordigi.conf preferences.
|
according to ordigi.conf preferences.
|
||||||
"""
|
"""
|
||||||
log_level = log.get_level(kwargs['verbose'])
|
log_level = log.get_level(kwargs['quiet'], kwargs['verbose'], kwargs['debug'])
|
||||||
log.console(LOG, level=log_level)
|
log.console(LOG, level=log_level)
|
||||||
|
|
||||||
paths, root = _get_paths(kwargs['subdirs'], kwargs['dest'])
|
paths, root = _get_paths(kwargs['subdirs'], kwargs['dest'])
|
||||||
|
@ -592,7 +613,7 @@ def _update(**kwargs):
|
||||||
Update media collection database.
|
Update media collection database.
|
||||||
"""
|
"""
|
||||||
root = Path(kwargs['path']).expanduser().absolute()
|
root = Path(kwargs['path']).expanduser().absolute()
|
||||||
log_level = log.get_level(kwargs['verbose'])
|
log_level = log.get_level(kwargs['quiet'], kwargs['verbose'], kwargs['debug'])
|
||||||
log.console(LOG, level=log_level)
|
log.console(LOG, level=log_level)
|
||||||
|
|
||||||
collection = Collection(root)
|
collection = Collection(root)
|
||||||
|
|
|
@ -494,6 +494,7 @@ class SortMedias:
|
||||||
self.summary = Summary(self.root)
|
self.summary = Summary(self.root)
|
||||||
|
|
||||||
# Attributes
|
# Attributes
|
||||||
|
self.input = request.Input()
|
||||||
self.theme = request.load_theme()
|
self.theme = request.load_theme()
|
||||||
|
|
||||||
def _checkcomp(self, dest_path, src_checksum):
|
def _checkcomp(self, dest_path, src_checksum):
|
||||||
|
@ -580,14 +581,10 @@ class SortMedias:
|
||||||
self.log.warning(f'Target directory {dir_path} is a file')
|
self.log.warning(f'Target directory {dir_path} is a file')
|
||||||
# Rename the src_file
|
# Rename the src_file
|
||||||
if self.interactive:
|
if self.interactive:
|
||||||
prompt = [
|
answer = self.input.text(
|
||||||
inquirer.Text(
|
"New name for" f"'{dir_path.name}' file"
|
||||||
'file_path',
|
)
|
||||||
message="New name for" f"'{dir_path.name}' file",
|
file_path = dir_path.parent / answer
|
||||||
),
|
|
||||||
]
|
|
||||||
answers = inquirer.prompt(prompt, theme=self.theme)
|
|
||||||
file_path = dir_path.parent / answers['file_path']
|
|
||||||
else:
|
else:
|
||||||
file_path = dir_path.parent / (dir_path.name + '_file')
|
file_path = dir_path.parent / (dir_path.name + '_file')
|
||||||
|
|
||||||
|
@ -760,6 +757,7 @@ class Collection(SortMedias):
|
||||||
self.paths,
|
self.paths,
|
||||||
root,
|
root,
|
||||||
self.opt['Exif'],
|
self.opt['Exif'],
|
||||||
|
{},
|
||||||
self.db,
|
self.db,
|
||||||
self.opt['Terminal']['interactive'],
|
self.opt['Terminal']['interactive'],
|
||||||
)
|
)
|
||||||
|
@ -867,7 +865,20 @@ class Collection(SortMedias):
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def check_db(self):
|
def check_file(self, file_path):
|
||||||
|
self.medias.checksums[file_path] = utils.checksum(file_path)
|
||||||
|
if self._check_file(file_path, self.medias.checksums[file_path]):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# We d'ont want to silently ignore or correct this without
|
||||||
|
# resetting the cache as is could be due to file corruption
|
||||||
|
self.log.error(f'modified or corrupted file.')
|
||||||
|
self.log.info(
|
||||||
|
'Use ordigi update --checksum or --reset-cache, check database integrity or try to restore the file'
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def check_db(self, checksums=True):
|
||||||
"""
|
"""
|
||||||
Check if db FilePath match to collection filesystem
|
Check if db FilePath match to collection filesystem
|
||||||
:returns: bool
|
:returns: bool
|
||||||
|
@ -876,18 +887,11 @@ class Collection(SortMedias):
|
||||||
db_rows = [row['FilePath'] for row in self.db.sqlite.get_rows('metadata')]
|
db_rows = [row['FilePath'] for row in self.db.sqlite.get_rows('metadata')]
|
||||||
for file_path in file_paths:
|
for file_path in file_paths:
|
||||||
result = self.file_in_db(file_path, db_rows)
|
result = self.file_in_db(file_path, db_rows)
|
||||||
checksum = utils.checksum(file_path)
|
|
||||||
if not result:
|
if not result:
|
||||||
self.log.error('Db data is not accurate')
|
self.log.error('Db data is not accurate')
|
||||||
self.log.info(f'{file_path} not in db')
|
self.log.info(f'{file_path} not in db')
|
||||||
return False
|
return False
|
||||||
elif not self._check_file(file_path, checksum):
|
elif checksums and not self.check_file(file_path):
|
||||||
# We d'ont want to silently ignore or correct this without
|
|
||||||
# resetting the cache as is could be due to file corruption
|
|
||||||
self.log.error(f'modified or corrupted file.')
|
|
||||||
self.log.info(
|
|
||||||
'Use ordigi update --checksum or --reset-cache, check database integrity or try to restore the file'
|
|
||||||
)
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
nb_files = len(file_paths)
|
nb_files = len(file_paths)
|
||||||
|
@ -906,10 +910,10 @@ class Collection(SortMedias):
|
||||||
self.log.error('Db data is not accurate run `ordigi update`')
|
self.log.error('Db data is not accurate run `ordigi update`')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
def _init_check_db(self, loc=None):
|
def _init_check_db(self, checksums=True, loc=None):
|
||||||
if self.db.sqlite.is_empty('metadata'):
|
if self.db.sqlite.is_empty('metadata'):
|
||||||
self.init(loc)
|
self.init(loc)
|
||||||
elif not self.check_db():
|
elif not self.check_db(checksums):
|
||||||
self.log.error('Db data is not accurate run `ordigi update`')
|
self.log.error('Db data is not accurate run `ordigi update`')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
@ -938,6 +942,7 @@ class Collection(SortMedias):
|
||||||
db_rows = list(self.db.sqlite.get_rows('metadata'))
|
db_rows = list(self.db.sqlite.get_rows('metadata'))
|
||||||
invalid_db_rows = set()
|
invalid_db_rows = set()
|
||||||
db_paths = set()
|
db_paths = set()
|
||||||
|
self.log.info(f"Update database:")
|
||||||
for db_row in db_rows:
|
for db_row in db_rows:
|
||||||
abspath = self.root / db_row['FilePath']
|
abspath = self.root / db_row['FilePath']
|
||||||
if abspath not in file_paths:
|
if abspath not in file_paths:
|
||||||
|
@ -949,15 +954,17 @@ class Collection(SortMedias):
|
||||||
relpath = os.path.relpath(file_path, self.root)
|
relpath = os.path.relpath(file_path, self.root)
|
||||||
metadata = {}
|
metadata = {}
|
||||||
|
|
||||||
checksum = utils.checksum(file_path)
|
self.medias.checksums[file_path] = utils.checksum(file_path)
|
||||||
if not self._check_file(file_path, checksum) and update_checksum:
|
if (
|
||||||
|
not self._check_file(file_path, self.medias.checksums[file_path])
|
||||||
|
and update_checksum
|
||||||
|
):
|
||||||
# metatata will fill checksum from file
|
# metatata will fill checksum from file
|
||||||
metadata = self.medias.get_metadata(
|
metadata = self.medias.get_metadata(file_path, self.root, loc=loc)
|
||||||
file_path, self.root, checksum, loc=loc
|
|
||||||
)
|
|
||||||
metadata['file_path'] = relpath
|
metadata['file_path'] = relpath
|
||||||
# set row attribute to the file
|
# set row attribute to the file
|
||||||
self.db.add_file_data(metadata)
|
self.db.add_file_data(metadata)
|
||||||
|
self.log.info(f"Update '{file_path}' checksum to db")
|
||||||
self.summary.append('update', file_path)
|
self.summary.append('update', file_path)
|
||||||
|
|
||||||
# If file not in database
|
# If file not in database
|
||||||
|
@ -978,11 +985,13 @@ class Collection(SortMedias):
|
||||||
break
|
break
|
||||||
# set row attribute to the file
|
# set row attribute to the file
|
||||||
self.db.add_file_data(metadata)
|
self.db.add_file_data(metadata)
|
||||||
|
self.log.info(f"Add '{file_path}' to db")
|
||||||
self.summary.append('update', file_path)
|
self.summary.append('update', file_path)
|
||||||
|
|
||||||
# Finally delete invalid rows
|
# Finally delete invalid rows
|
||||||
for row in invalid_db_rows:
|
for row in invalid_db_rows:
|
||||||
self.db.sqlite.delete_filepath(row['FilePath'])
|
self.db.sqlite.delete_filepath(row['FilePath'])
|
||||||
|
self.log.info(f"Delete invalid row : '{row['FilePath']}' from db")
|
||||||
|
|
||||||
return self.summary
|
return self.summary
|
||||||
|
|
||||||
|
@ -1057,7 +1066,7 @@ class Collection(SortMedias):
|
||||||
Sort files into appropriate folder
|
Sort files into appropriate folder
|
||||||
"""
|
"""
|
||||||
# Check db
|
# Check db
|
||||||
self._init_check_db(loc)
|
self._init_check_db(loc=loc)
|
||||||
|
|
||||||
path_format = self.opt['Path']['path_format']
|
path_format = self.opt['Path']['path_format']
|
||||||
self.log.debug(f'path_format: {path_format}')
|
self.log.debug(f'path_format: {path_format}')
|
||||||
|
@ -1189,35 +1198,43 @@ class Collection(SortMedias):
|
||||||
|
|
||||||
def edit_metadata(self, paths, keys, loc=None, overwrite=False):
|
def edit_metadata(self, paths, keys, loc=None, overwrite=False):
|
||||||
"""Edit metadata and exif data for given key"""
|
"""Edit metadata and exif data for given key"""
|
||||||
self._init_check_db()
|
|
||||||
|
|
||||||
|
if self.db.sqlite.is_empty('metadata'):
|
||||||
|
self.init(loc)
|
||||||
for file_path, media in self.medias.get_medias_datas(paths, loc=loc):
|
for file_path, media in self.medias.get_medias_datas(paths, loc=loc):
|
||||||
|
result = False
|
||||||
media.metadata['file_path'] = os.path.relpath(file_path, self.root)
|
media.metadata['file_path'] = os.path.relpath(file_path, self.root)
|
||||||
|
if not self.check_file(file_path):
|
||||||
|
self.log.error('Db data is not accurate run `ordigi update`')
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
exif = WriteExif(
|
||||||
|
file_path,
|
||||||
|
media.metadata,
|
||||||
|
ignore_tags=self.opt['Exif']['ignore_tags'],
|
||||||
|
)
|
||||||
|
|
||||||
for key in keys:
|
for key in keys:
|
||||||
print()
|
print()
|
||||||
value = media.metadata[key]
|
value = media.metadata[key]
|
||||||
if overwrite or not value:
|
if overwrite or not value:
|
||||||
print(f"FILE: '{file_path}'")
|
print(f"FILE: '{file_path}'")
|
||||||
if overwrite:
|
if overwrite and value:
|
||||||
print(f"{key}: '{value}'")
|
print(f"{key}: '{value}'")
|
||||||
if overwrite or not value:
|
if overwrite or not value:
|
||||||
# Prompt value for given key for file_path
|
# Prompt value for given key for file_path
|
||||||
prompt = [
|
answer = self.input.text(key)
|
||||||
inquirer.Text('value', message=key),
|
# Check value
|
||||||
]
|
|
||||||
answer = inquirer.prompt(prompt, theme=self.theme)
|
|
||||||
# answer = {'value': '03-12-2021 08:12:35'}
|
|
||||||
# Validate value
|
|
||||||
if key in ('date_original', 'date_created', 'date_modified'):
|
if key in ('date_original', 'date_created', 'date_modified'):
|
||||||
# Check date format
|
# Check date format
|
||||||
value = media.get_date_format(answer['value'])
|
value = media.get_date_format(answer)
|
||||||
else:
|
else:
|
||||||
value = answer['value']
|
value = answer
|
||||||
if not value.isalnum():
|
while not value.isalnum():
|
||||||
|
if not value: break
|
||||||
print("Invalid entry, use alphanumeric chars")
|
print("Invalid entry, use alphanumeric chars")
|
||||||
value = inquirer.prompt(prompt, theme=self.theme)
|
value = inquirer.prompt(prompt, theme=self.theme)
|
||||||
|
|
||||||
result = False
|
|
||||||
if value:
|
if value:
|
||||||
media.metadata[key] = value
|
media.metadata[key] = value
|
||||||
if key == 'location':
|
if key == 'location':
|
||||||
|
@ -1228,40 +1245,25 @@ class Collection(SortMedias):
|
||||||
media.set_location_from_coordinates(loc)
|
media.set_location_from_coordinates(loc)
|
||||||
|
|
||||||
# Update exif data
|
# Update exif data
|
||||||
if key in (
|
if key == 'location':
|
||||||
'date_original',
|
result = exif.set_key_values(
|
||||||
'album',
|
'latitude', media.metadata['latitude']
|
||||||
'title',
|
|
||||||
'latitude',
|
|
||||||
'location',
|
|
||||||
'longitude',
|
|
||||||
'latitude_ref',
|
|
||||||
'longitude_ref',
|
|
||||||
):
|
|
||||||
exif = WriteExif(
|
|
||||||
file_path,
|
|
||||||
media.metadata,
|
|
||||||
ignore_tags=self.opt['Exif']['ignore_tags'],
|
|
||||||
)
|
)
|
||||||
if key == 'location':
|
result = exif.set_key_values(
|
||||||
result = exif.set_key_values(
|
'longitude', media.metadata['longitude']
|
||||||
'latitude', media.metadata['latitude']
|
)
|
||||||
)
|
elif key in exif.get_tags().keys():
|
||||||
result = exif.set_key_values(
|
result = exif.set_key_values(key, value)
|
||||||
'longitude', media.metadata['longitude']
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
result = exif.set_key_values(key, value)
|
|
||||||
|
|
||||||
# Update checksum
|
# Update checksum
|
||||||
media.metadata['checksum'] = utils.checksum(file_path)
|
media.metadata['checksum'] = utils.checksum(file_path)
|
||||||
|
|
||||||
# Update database
|
# Update database
|
||||||
self.db.add_file_data(media.metadata)
|
self.db.add_file_data(media.metadata)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
self.summary.append('update', True, file_path)
|
self.summary.append('update', True, file_path)
|
||||||
else:
|
else:
|
||||||
self.summary.append('update', False, file_path)
|
self.summary.append('update', False, file_path)
|
||||||
|
|
||||||
return self.summary
|
return self.summary
|
||||||
|
|
|
@ -46,9 +46,16 @@ def file_logger(logger, file, level=30):
|
||||||
logger.addHandler(handler)
|
logger.addHandler(handler)
|
||||||
|
|
||||||
|
|
||||||
def get_level(verbose):
|
def get_level(quiet=False, verbose=False, debug=False, num=None):
|
||||||
"""Return int logging level from string"""
|
"""Return int logging level from command line args"""
|
||||||
if verbose.isnumeric():
|
if num and num.isnumeric():
|
||||||
return int(verbose)
|
return int(verbose)
|
||||||
|
|
||||||
return int(logging.getLevelName(verbose))
|
if debug:
|
||||||
|
return int(logging.getLevelName('DEBUG'))
|
||||||
|
if verbose:
|
||||||
|
return int(logging.getLevelName('INFO'))
|
||||||
|
if quiet:
|
||||||
|
return int(logging.getLevelName('ERROR'))
|
||||||
|
|
||||||
|
return int(logging.getLevelName('WARNING'))
|
||||||
|
|
|
@ -345,11 +345,8 @@ class Media(ReadExif):
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
if not answers['date_list']:
|
if not answers['date_list']:
|
||||||
prompt = [
|
answer = self.prompt.text("date")
|
||||||
inquirer.Text('date_custom', message="date"),
|
return self.get_date_format(answer)
|
||||||
]
|
|
||||||
answers = inquirer.prompt(prompt, theme=self.theme)
|
|
||||||
return self.get_date_format(answers['date_custom'])
|
|
||||||
|
|
||||||
return answers['date_list']
|
return answers['date_list']
|
||||||
|
|
||||||
|
@ -467,17 +464,12 @@ class Media(ReadExif):
|
||||||
default=f'{album}',
|
default=f'{album}',
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
prompt = [
|
|
||||||
inquirer.Text('custom', message="album"),
|
|
||||||
]
|
|
||||||
|
|
||||||
answers = inquirer.prompt(choices_list, theme=self.theme)
|
answers = inquirer.prompt(choices_list, theme=self.theme)
|
||||||
if not answers:
|
if not answers:
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
if not answers['album']:
|
if not answers['album']:
|
||||||
answers = inquirer.prompt(prompt, theme=self.theme)
|
return self.input.text("album")
|
||||||
return answers['custom']
|
|
||||||
|
|
||||||
return answers['album']
|
return answers['album']
|
||||||
|
|
||||||
|
@ -646,6 +638,7 @@ class Medias:
|
||||||
paths,
|
paths,
|
||||||
root,
|
root,
|
||||||
exif_options,
|
exif_options,
|
||||||
|
checksums=None,
|
||||||
db=None,
|
db=None,
|
||||||
interactive=False,
|
interactive=False,
|
||||||
):
|
):
|
||||||
|
@ -658,6 +651,11 @@ class Medias:
|
||||||
self.root = root
|
self.root = root
|
||||||
|
|
||||||
# Options
|
# Options
|
||||||
|
if checksums:
|
||||||
|
self.checksums = checksums
|
||||||
|
else:
|
||||||
|
self.checksums = {}
|
||||||
|
|
||||||
self.exif_opt = exif_options
|
self.exif_opt = exif_options
|
||||||
|
|
||||||
self.ignore_tags = self.exif_opt['ignore_tags']
|
self.ignore_tags = self.exif_opt['ignore_tags']
|
||||||
|
@ -684,7 +682,14 @@ class Medias:
|
||||||
|
|
||||||
return media
|
return media
|
||||||
|
|
||||||
def get_media_data(self, file_path, src_dir, checksum=None, loc=None):
|
def get_media_data(self, file_path, src_dir, loc=None):
|
||||||
|
"""Get media class instance with metadata"""
|
||||||
|
|
||||||
|
if self.checksums and file_path in self.checksums.keys():
|
||||||
|
checksum = self.checksums[file_path]
|
||||||
|
else:
|
||||||
|
checksum = None
|
||||||
|
|
||||||
media = self.get_media(file_path, src_dir, checksum)
|
media = self.get_media(file_path, src_dir, checksum)
|
||||||
media.get_metadata(
|
media.get_metadata(
|
||||||
self.root, loc, self.db.sqlite, self.exif_opt['cache']
|
self.root, loc, self.db.sqlite, self.exif_opt['cache']
|
||||||
|
@ -692,9 +697,9 @@ class Medias:
|
||||||
|
|
||||||
return media
|
return media
|
||||||
|
|
||||||
def get_metadata(self, src_path, src_dir, checksum=None, loc=None):
|
def get_metadata(self, src_path, src_dir, loc=None):
|
||||||
"""Get metadata"""
|
"""Get metadata"""
|
||||||
return self.get_media_data(src_path, src_dir, checksum, loc).metadata
|
return self.get_media_data(src_path, src_dir, loc).metadata
|
||||||
|
|
||||||
def get_paths(self, src_dirs, imp=False):
|
def get_paths(self, src_dirs, imp=False):
|
||||||
"""Get paths"""
|
"""Get paths"""
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import inquirer
|
import inquirer
|
||||||
from blessed import Terminal
|
from blessed import Terminal
|
||||||
|
from colorama import init,Fore,Style,Back
|
||||||
|
|
||||||
term = Terminal()
|
term = Terminal()
|
||||||
|
|
||||||
|
@ -34,6 +35,15 @@ def load_theme():
|
||||||
return inquirer.themes.load_theme_from_dict(custom_theme)
|
return inquirer.themes.load_theme_from_dict(custom_theme)
|
||||||
|
|
||||||
|
|
||||||
|
class Input():
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
init()
|
||||||
|
|
||||||
|
def text(self, message):
|
||||||
|
return input(f'{Fore.BLUE}[{Fore.YELLOW}?{Fore.BLUE}]{Fore.WHITE} {message}: ')
|
||||||
|
|
||||||
|
|
||||||
# def edit_prompt(self, key: str, value: str) -> str:
|
# def edit_prompt(self, key: str, value: str) -> str:
|
||||||
# print(f"Date conflict for file: {self.file_path}")
|
# print(f"Date conflict for file: {self.file_path}")
|
||||||
|
|
|
@ -5,6 +5,7 @@ import pytest
|
||||||
import inquirer
|
import inquirer
|
||||||
|
|
||||||
from ordigi import cli
|
from ordigi import cli
|
||||||
|
from ordigi.request import Input
|
||||||
|
|
||||||
CONTENT = "content"
|
CONTENT = "content"
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ class TestOrdigi:
|
||||||
def setup_class(cls, sample_files_paths):
|
def setup_class(cls, sample_files_paths):
|
||||||
cls.runner = CliRunner()
|
cls.runner = CliRunner()
|
||||||
cls.src_path, cls.file_paths = sample_files_paths
|
cls.src_path, cls.file_paths = sample_files_paths
|
||||||
cls.logger_options = (('--verbose', 'DEBUG'),)
|
cls.logger_options = ('--debug',)
|
||||||
cls.filter_options = (
|
cls.filter_options = (
|
||||||
('--ignore-tags', 'CreateDate'),
|
('--ignore-tags', 'CreateDate'),
|
||||||
('--ext', 'jpg'),
|
('--ext', 'jpg'),
|
||||||
|
@ -81,21 +82,23 @@ class TestOrdigi:
|
||||||
|
|
||||||
def test_edit(self, monkeypatch):
|
def test_edit(self, monkeypatch):
|
||||||
|
|
||||||
bool_options = ()
|
bool_options = (
|
||||||
|
*self.logger_options,
|
||||||
|
)
|
||||||
|
|
||||||
arg_options = (
|
arg_options = (
|
||||||
*self.logger_options,
|
|
||||||
*self.filter_options,
|
*self.filter_options,
|
||||||
)
|
)
|
||||||
|
|
||||||
def mockreturn(prompt, theme):
|
def mockreturn(self, message):
|
||||||
return {'value': '03-12-2021 08:12:35'}
|
return '03-12-2021 08:12:35'
|
||||||
|
|
||||||
monkeypatch.setattr(inquirer, 'prompt', mockreturn)
|
monkeypatch.setattr(Input, 'text', mockreturn)
|
||||||
|
|
||||||
args = (
|
args = (
|
||||||
'--key',
|
'--key',
|
||||||
'date_original',
|
'date_original',
|
||||||
|
'--overwrite',
|
||||||
str(self.src_path.joinpath('test_exif/photo.png')),
|
str(self.src_path.joinpath('test_exif/photo.png')),
|
||||||
str(self.src_path),
|
str(self.src_path),
|
||||||
)
|
)
|
||||||
|
@ -107,6 +110,7 @@ class TestOrdigi:
|
||||||
|
|
||||||
def test_sort(self):
|
def test_sort(self):
|
||||||
bool_options = (
|
bool_options = (
|
||||||
|
*self.logger_options,
|
||||||
# '--interactive',
|
# '--interactive',
|
||||||
'--dry-run',
|
'--dry-run',
|
||||||
'--album-from-folder',
|
'--album-from-folder',
|
||||||
|
@ -117,7 +121,6 @@ class TestOrdigi:
|
||||||
)
|
)
|
||||||
|
|
||||||
arg_options = (
|
arg_options = (
|
||||||
*self.logger_options,
|
|
||||||
*self.filter_options,
|
*self.filter_options,
|
||||||
('--path-format', '{%Y}/{folder}/{name}.{ext}'),
|
('--path-format', '{%Y}/{folder}/{name}.{ext}'),
|
||||||
|
|
||||||
|
@ -132,36 +135,29 @@ class TestOrdigi:
|
||||||
|
|
||||||
def test_clone(self, tmp_path):
|
def test_clone(self, tmp_path):
|
||||||
|
|
||||||
arg_options = (
|
|
||||||
*self.logger_options,
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
paths = (str(self.src_path), str(tmp_path))
|
paths = (str(self.src_path), str(tmp_path))
|
||||||
|
|
||||||
self.assert_cli(cli._init, [str(self.src_path)])
|
self.assert_cli(cli._init, [str(self.src_path)])
|
||||||
self.assert_cli(cli._clone, ['--dry-run', '--verbose', 'DEBUG', *paths])
|
self.assert_cli(cli._clone, ['--dry-run', *self.logger_options, *paths])
|
||||||
self.assert_cli(cli._clone, paths)
|
self.assert_cli(cli._clone, paths)
|
||||||
|
|
||||||
|
|
||||||
def assert_init(self):
|
def assert_init(self):
|
||||||
for opt, arg in self.logger_options:
|
self.assert_cli(cli._init, [*self.logger_options, str(self.src_path)])
|
||||||
self.assert_cli(cli._init, [opt, arg, str(self.src_path)])
|
|
||||||
|
|
||||||
def assert_update(self):
|
def assert_update(self):
|
||||||
file_path = Path(ORDIGI_PATH, 'samples/test_exif/photo.cr2')
|
file_path = Path(ORDIGI_PATH, 'samples/test_exif/photo.cr2')
|
||||||
dest_path = self.src_path / 'photo_moved.cr2'
|
dest_path = self.src_path / 'photo_moved.cr2'
|
||||||
shutil.copyfile(file_path, dest_path)
|
shutil.copyfile(file_path, dest_path)
|
||||||
for opt, arg in self.logger_options:
|
self.assert_cli(cli._update, [*self.logger_options, str(self.src_path)])
|
||||||
self.assert_cli(cli._update, [opt, arg, str(self.src_path)])
|
|
||||||
self.assert_cli(cli._update, ['--checksum', str(self.src_path)])
|
self.assert_cli(cli._update, ['--checksum', str(self.src_path)])
|
||||||
|
|
||||||
def assert_check(self):
|
def assert_check(self):
|
||||||
for opt, arg in self.logger_options:
|
self.assert_cli(cli._check, [*self.logger_options, str(self.src_path)])
|
||||||
self.assert_cli(cli._check, [opt, arg, str(self.src_path)])
|
|
||||||
|
|
||||||
def assert_clean(self):
|
def assert_clean(self):
|
||||||
bool_options = (
|
bool_options = (
|
||||||
|
*self.logger_options,
|
||||||
# '--interactive',
|
# '--interactive',
|
||||||
'--dry-run',
|
'--dry-run',
|
||||||
'--delete-excluded',
|
'--delete-excluded',
|
||||||
|
@ -171,7 +167,6 @@ class TestOrdigi:
|
||||||
)
|
)
|
||||||
|
|
||||||
arg_options = (
|
arg_options = (
|
||||||
*self.logger_options,
|
|
||||||
*self.filter_options,
|
*self.filter_options,
|
||||||
('--dedup-regex', r'\d{4}-\d{2}'),
|
('--dedup-regex', r'\d{4}-\d{2}'),
|
||||||
)
|
)
|
||||||
|
@ -192,6 +187,7 @@ class TestOrdigi:
|
||||||
|
|
||||||
def test_import(self, tmp_path):
|
def test_import(self, tmp_path):
|
||||||
bool_options = (
|
bool_options = (
|
||||||
|
*self.logger_options,
|
||||||
# '--interactive',
|
# '--interactive',
|
||||||
'--dry-run',
|
'--dry-run',
|
||||||
'--album-from-folder',
|
'--album-from-folder',
|
||||||
|
@ -202,7 +198,6 @@ class TestOrdigi:
|
||||||
)
|
)
|
||||||
|
|
||||||
arg_options = (
|
arg_options = (
|
||||||
*self.logger_options,
|
|
||||||
('--exclude', '.DS_Store'),
|
('--exclude', '.DS_Store'),
|
||||||
*self.filter_options,
|
*self.filter_options,
|
||||||
('--path-format', '{%Y}/{folder}/{stem}.{ext}'),
|
('--path-format', '{%Y}/{folder}/{stem}.{ext}'),
|
||||||
|
@ -218,6 +213,7 @@ class TestOrdigi:
|
||||||
|
|
||||||
def test_compare(self):
|
def test_compare(self):
|
||||||
bool_options = (
|
bool_options = (
|
||||||
|
*self.logger_options,
|
||||||
# '--interactive',
|
# '--interactive',
|
||||||
'--dry-run',
|
'--dry-run',
|
||||||
'--find-duplicates',
|
'--find-duplicates',
|
||||||
|
@ -225,7 +221,6 @@ class TestOrdigi:
|
||||||
)
|
)
|
||||||
|
|
||||||
arg_options = (
|
arg_options = (
|
||||||
*self.logger_options,
|
|
||||||
*self.filter_options,
|
*self.filter_options,
|
||||||
# ('--similar-to', ''),
|
# ('--similar-to', ''),
|
||||||
('--similarity', '65'),
|
('--similarity', '65'),
|
||||||
|
|
|
@ -8,13 +8,14 @@ import inquirer
|
||||||
|
|
||||||
from ordigi import LOG
|
from ordigi import LOG
|
||||||
from ordigi import constants
|
from ordigi import constants
|
||||||
|
from ordigi import utils
|
||||||
|
from ordigi.summary import Summary
|
||||||
from ordigi.collection import Collection, FPath, Paths
|
from ordigi.collection import Collection, FPath, Paths
|
||||||
from ordigi.exiftool import ExifTool, ExifToolCaching, exiftool_is_running, terminate_exiftool
|
from ordigi.exiftool import ExifTool, ExifToolCaching, exiftool_is_running, terminate_exiftool
|
||||||
from ordigi.geolocation import GeoLocation
|
from ordigi.geolocation import GeoLocation
|
||||||
from ordigi.media import Media, ReadExif
|
from ordigi.media import Media, ReadExif
|
||||||
from ordigi import utils
|
from ordigi.request import Input
|
||||||
from .conftest import randomize_files, randomize_db
|
from .conftest import randomize_files, randomize_db
|
||||||
from ordigi.summary import Summary
|
|
||||||
|
|
||||||
LOG.setLevel(10)
|
LOG.setLevel(10)
|
||||||
|
|
||||||
|
@ -169,7 +170,7 @@ class TestCollection:
|
||||||
path_format = 'test_exif/<city>/<%Y>-<name>.%l<ext>'
|
path_format = 'test_exif/<city>/<%Y>-<name>.%l<ext>'
|
||||||
summary = collection.sort_files([tmp_path], loc)
|
summary = collection.sort_files([tmp_path], loc)
|
||||||
|
|
||||||
self.assert_sort(summary, 26)
|
self.assert_sort(summary, 23)
|
||||||
|
|
||||||
shutil.copytree(tmp_path / 'test_exif', tmp_path / 'test_exif_copy')
|
shutil.copytree(tmp_path / 'test_exif', tmp_path / 'test_exif_copy')
|
||||||
collection.summary = Summary(tmp_path)
|
collection.summary = Summary(tmp_path)
|
||||||
|
@ -257,10 +258,10 @@ class TestCollection:
|
||||||
shutil.copytree(self.src_path, path)
|
shutil.copytree(self.src_path, path)
|
||||||
collection = Collection(path, {'cache': False})
|
collection = Collection(path, {'cache': False})
|
||||||
|
|
||||||
def mockreturn(prompt, theme):
|
def mockreturn(self, message):
|
||||||
return {'value': '03-12-2021 08:12:35'}
|
return '03-12-2021 08:12:35'
|
||||||
|
|
||||||
monkeypatch.setattr(inquirer, 'prompt', mockreturn)
|
monkeypatch.setattr(Input, 'text', mockreturn)
|
||||||
|
|
||||||
collection.edit_metadata({path}, {'date_original'}, overwrite=True)
|
collection.edit_metadata({path}, {'date_original'}, overwrite=True)
|
||||||
# check if db value is set
|
# check if db value is set
|
||||||
|
@ -278,10 +279,10 @@ class TestCollection:
|
||||||
collection = Collection(path, {'cache': False})
|
collection = Collection(path, {'cache': False})
|
||||||
loc = GeoLocation()
|
loc = GeoLocation()
|
||||||
|
|
||||||
def mockreturn(prompt, theme):
|
def mockreturn(self, message):
|
||||||
return {'value': 'lyon'}
|
return 'lyon'
|
||||||
|
|
||||||
monkeypatch.setattr(inquirer, 'prompt', mockreturn)
|
monkeypatch.setattr(Input, 'text', mockreturn)
|
||||||
|
|
||||||
collection.edit_metadata({path}, {'location'}, loc, True)
|
collection.edit_metadata({path}, {'location'}, loc, True)
|
||||||
# check if db value is set
|
# check if db value is set
|
||||||
|
|
111
todo.md
111
todo.md
|
@ -1,111 +0,0 @@
|
||||||
# NOW
|
|
||||||
|
|
||||||
- db integrity have not to be checked in media but in collection??
|
|
||||||
|
|
||||||
- build structure to store file path and info with metadata
|
|
||||||
metadatas[file_path] = {'checksum': value}. Init must select same files than
|
|
||||||
get_metadatata
|
|
||||||
|
|
||||||
- check edit_metadata again test with valid doc
|
|
||||||
- show exif metadata
|
|
||||||
- print all values and select some to edit
|
|
||||||
- dry run = no changes
|
|
||||||
|
|
||||||
- compare custom output folder similar to?
|
|
||||||
- ordigi-gui
|
|
||||||
- add name and dirpath options???
|
|
||||||
|
|
||||||
# TODO
|
|
||||||
Options:
|
|
||||||
--location --time
|
|
||||||
# -f overwrite metadata
|
|
||||||
--auto|-a: a set of option: geolocalisation, best match date, rename, album
|
|
||||||
from folder...
|
|
||||||
# --keep-folder option
|
|
||||||
# --rename
|
|
||||||
--confirm unsure operation
|
|
||||||
|
|
||||||
# Bugs
|
|
||||||
- summary
|
|
||||||
|
|
||||||
- set date original???, interactive mode...
|
|
||||||
|
|
||||||
- Faire en sorte que le programme ne plante pas...
|
|
||||||
- option to not update exif metadata...
|
|
||||||
## Exiftools
|
|
||||||
https://gitlab.com/TNThieding/exif
|
|
||||||
exiftool -akljklbum=tjkljkestjlj /tmp/pytest-of-cedric/pytest-12/test_sort_files0/2008-10-Oct/test_exif/2008-10-24_09-12-56-photo.nef
|
|
||||||
|
|
||||||
exiftool -album=tjkljkestjlj /tmp/pytest-of-cedric/pytest-12/test_sort_files0/2008-10-Oct/test_exif/2008-10-24_09-12-56-photo.nef
|
|
||||||
1 image files updated
|
|
||||||
|
|
||||||
Get result code....
|
|
||||||
|
|
||||||
|
|
||||||
## Doc use sphinx??
|
|
||||||
|
|
||||||
## Commands
|
|
||||||
- ordigi view/show
|
|
||||||
- ordigi search
|
|
||||||
- use tree to show paths?
|
|
||||||
|
|
||||||
|
|
||||||
# Pylint
|
|
||||||
https://pythonspeed.com/articles/pylint/
|
|
||||||
use config file
|
|
||||||
|
|
||||||
# Media:
|
|
||||||
|
|
||||||
# Test:
|
|
||||||
|
|
||||||
# enhancement
|
|
||||||
- summary: replace success by copied/moved/deleted
|
|
||||||
|
|
||||||
## Alias
|
|
||||||
alias ogi=ordigi
|
|
||||||
|
|
||||||
## Image analysis
|
|
||||||
https://pypi.org/project/google-cloud-vision/
|
|
||||||
https://googleapis.dev/python/vision/latest/index.html
|
|
||||||
https://www.datacamp.com/community/tutorials/beginner-guide-google-vision-api
|
|
||||||
|
|
||||||
|
|
||||||
## Album form folder
|
|
||||||
|
|
||||||
# Update
|
|
||||||
|
|
||||||
https://github.com/JohannesBuchner/imagehash
|
|
||||||
https://github.com/cw-somil/Duplicate-Remover
|
|
||||||
https://leons.im/posts/a-python-implementation-of-simhash-algorithm/
|
|
||||||
|
|
||||||
Visualy check similar image
|
|
||||||
https://www.pluralsight.com/guides/importing-image-data-into-numpy-arrays
|
|
||||||
https://stackoverflow.com/questions/56056054/add-check-boxes-to-scrollable-image-in-python
|
|
||||||
https://wellsr.com/python/python-image-manipulation-with-pillow-library/
|
|
||||||
kitty gird image?
|
|
||||||
https://fr.wikibooks.org/wiki/PyQt/PyQt_versus_wxPython
|
|
||||||
https://docs.python.org/3/faq/gui.html
|
|
||||||
https://docs.opencv.org/3.4/d3/df2/tutorial_py_basic_ops.html
|
|
||||||
https://stackoverflow.com/questions/52727332/python-tkinter-create-checkbox-list-from-listbox
|
|
||||||
|
|
||||||
|
|
||||||
Image gird method:
|
|
||||||
matplot
|
|
||||||
https://gist.github.com/lebedov/7018889ba47668c64bcf96aee82caec0
|
|
||||||
|
|
||||||
Tkinter
|
|
||||||
https://python-forum.io/thread-22700.html
|
|
||||||
https://stackoverflow.com/questions/43326282/how-can-i-use-images-in-a-tkinter-grid
|
|
||||||
|
|
||||||
wxwidget
|
|
||||||
https://wxpython.org/Phoenix/docs/html/wx.lib.agw.thumbnailctrl.html
|
|
||||||
|
|
||||||
|
|
||||||
Ability to change metadata to selection
|
|
||||||
|
|
||||||
Fix: change versvalidion number to 0.x99
|
|
||||||
|
|
||||||
https://github.com/andrewning/sortphotos/blob/master/src/sortphotos.py
|
|
||||||
|
|
||||||
# AFTER
|
|
||||||
|
|
30
workflow.md
30
workflow.md
|
@ -1,30 +0,0 @@
|
||||||
# Create virtual environment
|
|
||||||
nmkvirtualenv ordigi
|
|
||||||
|
|
||||||
# Work on it (activate and cd)
|
|
||||||
workon ordigi
|
|
||||||
|
|
||||||
# Install required dependencies
|
|
||||||
pip install -r requirements.txt
|
|
||||||
|
|
||||||
# Liked it to path
|
|
||||||
pip install -e .
|
|
||||||
|
|
||||||
# View file tree of path
|
|
||||||
tree /dest/path
|
|
||||||
|
|
||||||
# Test code
|
|
||||||
pylint ordigi/* -E
|
|
||||||
pylint ordigi/**
|
|
||||||
pytest --cov=ordigi --cov-report html tests/*.py
|
|
||||||
pip install --prefix=~/.local -e .
|
|
||||||
|
|
||||||
# config
|
|
||||||
|
|
||||||
## Path format
|
|
||||||
dirs_path=<%Y>/<%m-%b>-<city>-<folder>
|
|
||||||
name=<%Y%m%d-%H%M%S>-%u<original_name>|%u<basename>.%l<ext>
|
|
||||||
|
|
||||||
|
|
||||||
## run
|
|
||||||
ordigi import 220719.bkp -f -c -R collection
|
|
Loading…
Reference in New Issue