From a8e0fd63f92c5330bfb27aa8361d62f9cf4d6be6 Mon Sep 17 00:00:00 2001 From: Dima Gerasimov Date: Sun, 26 Aug 2018 10:34:42 +0100 Subject: [PATCH 01/10] initial --- .gitignore | 172 +++++++++++++++++++++++++++++++++++++++++++++ photos/__init__.py | 0 photos/__main__.py | 0 3 files changed, 172 insertions(+) create mode 100644 .gitignore create mode 100644 photos/__init__.py create mode 100644 photos/__main__.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b539013 --- /dev/null +++ b/.gitignore @@ -0,0 +1,172 @@ + +# Created by https://www.gitignore.io/api/python,emacs + +### Emacs ### +# -*- mode: gitignore; -*- +*~ +\#*\# +/.emacs.desktop +/.emacs.desktop.lock +*.elc +auto-save-list +tramp +.\#* + +# Org-mode +.org-id-locations +*_archive + +# flymake-mode +*_flymake.* + +# eshell files +/eshell/history +/eshell/lastdir + +# elpa packages +/elpa/ + +# reftex files +*.rel + +# AUCTeX auto folder +/auto/ + +# cask packages +.cask/ +dist/ + +# Flycheck +flycheck_*.el + +# server auth directory +/server/ + +# projectiles files +.projectile + +# directory configuration +.dir-locals.el + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +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/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# 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/ + +### Python Patch ### +.venv/ + +### Python.VirtualEnv Stack ### +# Virtualenv +# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ +[Bb]in +[Ii]nclude +[Ll]ib +[Ll]ib64 +[Ll]ocal +[Ss]cripts +pyvenv.cfg +pip-selfcheck.json + + +# End of https://www.gitignore.io/api/python,emacs diff --git a/photos/__init__.py b/photos/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/photos/__main__.py b/photos/__main__.py new file mode 100644 index 0000000..e69de29 From 0a68e3000d35935cdefdd3ae0f5f5aaa5f0f8938 Mon Sep 17 00:00:00 2001 From: Dima Gerasimov Date: Sun, 26 Aug 2018 14:07:26 +0100 Subject: [PATCH 02/10] providing geo and timestamp, caching, CI --- ci.sh | 10 +++ photos/__init__.py | 207 +++++++++++++++++++++++++++++++++++++++++++++ photos/__main__.py | 24 ++++++ requirements.txt | 5 ++ update_cache | 7 ++ 5 files changed, 253 insertions(+) create mode 100755 ci.sh create mode 100644 requirements.txt create mode 100755 update_cache diff --git a/ci.sh b/ci.sh new file mode 100755 index 0000000..0daaac3 --- /dev/null +++ b/ci.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +cd "$(this_dir)" || exit + +. ~/bash_ci + +ci_run mypy photos +ci_run pylint -E photos + +ci_report_errors diff --git a/photos/__init__.py b/photos/__init__.py index e69de29..3d90b32 100644 --- a/photos/__init__.py +++ b/photos/__init__.py @@ -0,0 +1,207 @@ +from datetime import datetime +import itertools +import os +from os.path import join +from typing import Tuple, Dict, Optional, NamedTuple, Iterator, Iterable, List + +from geopy.geocoders import Nominatim # type: ignore + +import magic # type: ignore + +import PIL.Image # type: ignore +from PIL.ExifTags import TAGS, GPSTAGS # type: ignore + +from kython import json_load + +import logging +def get_logger(): + return logging.getLogger('photo-provider') + + +geolocator = Nominatim() # TODO does it cache?? +mime = magic.Magic(mime=True) + +# TODO hmm, instead geo could be a dynamic property... although a bit wasteful + +PATHS = [ + "***REMOVED***", + "***REMOVED***", + "***REMOVED***", +] +# TODO could use other pathes I suppose? +# TODO or maybe just use symlinks +# TODO however then won't be accessible from dropbox + +# PATH = "***REMOVED***/***REMOVED***" +# PATH = "***REMOVED***/***REMOVED***" + +CACHE_PATH = "***REMOVED***" + +LatLon = Tuple[float, float] + +# TODO PIL.ExifTags.TAGS + +DATETIME = "DateTimeOriginal" +LAT = "GPSLatitude" +LAT_REF = "GPSLatitudeRef" +LON = "GPSLongitude" +LON_REF = "GPSLongitudeRef" +GPSINFO = "GPSInfo" + +# TODO kython?? +def get_exif_data(image): + """Returns a dictionary from the exif data of an PIL Image item. Also converts the GPS Tags""" + exif_data = {} + info = image._getexif() + if info: + for tag, value in info.items(): + decoded = TAGS.get(tag, tag) + if decoded == GPSINFO: + gps_data = {} + for t in value: + sub_decoded = GPSTAGS.get(t, t) + gps_data[sub_decoded] = value[t] + + exif_data[decoded] = gps_data + else: + exif_data[decoded] = value + + return exif_data + +def to_degree(value): + """Helper function to convert the GPS coordinates + stored in the EXIF to degress in float format""" + d0 = value[0][0] + d1 = value[0][1] + d = float(d0) / float(d1) + m0 = value[1][0] + m1 = value[1][1] + m = float(m0) / float(m1) + + s0 = value[2][0] + s1 = value[2][1] + s = float(s0) / float(s1) + + return d + (m / 60.0) + (s / 3600.0) + +def convert(cstr, ref: str): + val = to_degree(cstr) + if ref == 'S' or ref == 'W': + val = -val + return val + + +class Photo(NamedTuple): + path: str + dt: Optional[datetime] + geo: Optional[LatLon] + # TODO can we always extract date? I guess not... + + @property + def tags(self) -> List[str]: # TODO + return [] + +def _try_photo(photo: str, mtype: str, dgeo: Optional[LatLon]) -> Optional[Photo]: + logger = get_logger() + + geo: Optional[LatLon] + + dt: Optional[datetime] = None + geo = dgeo + if any(x in mtype for x in {'image/png', 'image/x-ms-bmp', 'video'}): + logger.info(f"Skipping geo extraction for {photo} due to mime {mtype}") + else: + edata: Dict + try: + with PIL.Image.open(photo) as fo: + edata = get_exif_data(fo) + except Exception as e: + logger.warning(f"Couln't get exif for {photo}") # TODO meh + logger.exception(e) + else: + dtimes = edata.get('DateTimeOriginal', None) + if dtimes is not None: + try: + dtimes = dtimes.replace(' 24', ' 00') # jeez maybe log it? + if dtimes == "0000:00:00 00:00:00": + logger.info(f"Bad exif timestamp {dtimes} for {photo}") + else: + dt = datetime.strptime(dtimes, '%Y:%m:%d %H:%M:%S') + # # TODO timezone is local, should take into account... + except Exception as e: + logger.error(f"Error while trying to extract date for {photo}") + logger.exception(e) + + meta = edata.get(GPSINFO, {}) + if LAT in meta and LON in meta: + lat = convert(meta[LAT], meta[LAT_REF]) + lon = convert(meta[LON], meta[LON_REF]) + geo = (lat, lon) + + return Photo(photo, dt, geo) + # plink = f"file://{photo}" + # plink = "https://upload.wikimedia.org/wikipedia/commons/thumb/1/19/Ichthyornis_Clean.png/800px-Ichthyornis_Clean.png" + # yield (geo, src.color, plink) + + +# if geo information is missing from photo, you can specify it manually in geo.json file +def iter_photos() -> Iterator[Photo]: + logger = get_logger() + + geos: List[LatLon] = [] # stack of geos so we could use the most specific one + # TODO could have this for all meta? e.g. time + for d, _, files in itertools.chain.from_iterable((os.walk(pp) for pp in PATHS)): + logger.info(f"Processing {d}") + + geof = join(d, 'geo.json') + cgeo = None + if os.path.isfile(geof): + j: Dict + with open(geof, 'r') as fo: + j = json_load(fo) + if 'name' in j: + g = geolocator.geocode(j['name']) + geo = (g.latitude, g.longitude) + else: + geo = j['lat'], j['lon'] + geos.append(geo) + + for f in sorted(files): + photo = join(d, f) + mtype = mime.from_file(photo) + + IGNORED = { + 'application', + 'audio', + 'text', + 'inode', + } + if any(i in mtype for i in IGNORED): + logger.info(f"Ignoring {photo} due to mime {mtype}") + continue + + try: + dgeo = None if len(geos) == 0 else geos[-1] + p = _try_photo(photo, mtype, dgeo) + if p is not None: + yield p + except Exception as e: + raise RuntimeError(f'Error while processing {photo}') from e + + if cgeo is not None: + geos.pop() + +def get_photos(cached: bool=False) -> Iterable[Photo]: + import dill # type: ignore + if cached: + with open(CACHE_PATH, 'rb') as fo: + preph = dill.load(fo) + return [Photo(**p._asdict()) for p in preph] # meh. but otherwise it's not serialising methods... + else: + return list(iter_photos()) + +def update_cache(): + import dill # type: ignore + photos = get_photos(cached=False) + with open(CACHE_PATH, 'wb') as fo: + dill.dump(photos, fo) diff --git a/photos/__main__.py b/photos/__main__.py index e69de29..04b4053 100644 --- a/photos/__main__.py +++ b/photos/__main__.py @@ -0,0 +1,24 @@ +import logging +logging.basicConfig(level=logging.INFO) + +from kython.logging import setup_logzero + +from photos import get_photos, iter_photos, get_logger + +setup_logzero(get_logger(), level=logging.DEBUG) + +import sys + +if len(sys.argv) > 1: + cmd = sys.argv[1] + if cmd == "update_cache": + from photos import update_cache, get_photos + update_cache() + get_photos(cached=True) + else: + raise RuntimeError(f"Unknown command {cmd}") +else: + for p in iter_photos(): + pass + # TODO need datetime! + # print(p) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..dc87a9e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +geopy +magic + +# optional if you want caching +dill diff --git a/update_cache b/update_cache new file mode 100755 index 0000000..6daf62c --- /dev/null +++ b/update_cache @@ -0,0 +1,7 @@ +#!/bin/bash +set -eu + +cd "$(dirname "$0")" + +python3 -m photos update_cache + From b5834e515621f514a3e20f8d2e67b165205070af Mon Sep 17 00:00:00 2001 From: Dima Gerasimov Date: Mon, 1 Oct 2018 23:19:38 +0100 Subject: [PATCH 03/10] Extract date from photos --- photos/__init__.py | 57 ++++++++++++++++++++++++++++++++++++++++++++-- photos/__main__.py | 1 + run | 3 +++ 3 files changed, 59 insertions(+), 2 deletions(-) create mode 100755 run diff --git a/photos/__init__.py b/photos/__init__.py index 3d90b32..80445a0 100644 --- a/photos/__init__.py +++ b/photos/__init__.py @@ -1,7 +1,8 @@ from datetime import datetime import itertools import os -from os.path import join +from os.path import join, basename +import re from typing import Tuple, Dict, Optional, NamedTuple, Iterator, Iterable, List from geopy.geocoders import Nominatim # type: ignore @@ -23,6 +24,33 @@ mime = magic.Magic(mime=True) # TODO hmm, instead geo could be a dynamic property... although a bit wasteful +# TODO insta photos should have instagram tag? + +# TODO sokino -- wrong timestamp + +_REGEXES = [re.compile(rs) for rs in [ + r'***REMOVED***', + r'***REMOVED***', + # TODO eh, some photos from ***REMOVED*** -- which is clearly bad datetime! like a default setting + # TODO mm. maybe have expected datetime ranges for photos and discard everything else? some cameras looks like they god bad timestamps +]] + +def ignore_path(p: str): + for reg in _REGEXES: + if reg.search(p): + return True + return False + + +_DT_REGEX = re.compile(r'\D(\d{8})\D*(\d{6})\D') +def dt_from_path(p: str) -> Optional[datetime]: + name = basename(p) + mm = _DT_REGEX.search(name) + if mm is None: + return None + dates = mm.group(1) + mm.group(2) + return datetime.strptime(dates, "%Y%m%d%H%M%S") + PATHS = [ "***REMOVED***", "***REMOVED***", @@ -129,7 +157,7 @@ def _try_photo(photo: str, mtype: str, dgeo: Optional[LatLon]) -> Optional[Photo dt = datetime.strptime(dtimes, '%Y:%m:%d %H:%M:%S') # # TODO timezone is local, should take into account... except Exception as e: - logger.error(f"Error while trying to extract date for {photo}") + logger.error(f"Error while trying to extract date from EXIF {photo}") logger.exception(e) meta = edata.get(GPSINFO, {}) @@ -137,17 +165,38 @@ def _try_photo(photo: str, mtype: str, dgeo: Optional[LatLon]) -> Optional[Photo lat = convert(meta[LAT], meta[LAT_REF]) lon = convert(meta[LON], meta[LON_REF]) geo = (lat, lon) + if dt is None: + try: + dt = dt_from_path(photo) # ok, last try.. + except Exception as e: + logger.error(f"Error while trying to extract date from name {photo}") + logger.exception(e) + return Photo(photo, dt, geo) # plink = f"file://{photo}" # plink = "https://upload.wikimedia.org/wikipedia/commons/thumb/1/19/Ichthyornis_Clean.png/800px-Ichthyornis_Clean.png" # yield (geo, src.color, plink) +# TODO ugh. need something like this, but tedious to reimplement.. +# class Walker: +# def __init__(self, root: str) -> None: +# self.root = root + +# def walk(self): + + +# def step(self, cur, dirs, files): +# pass + # if geo information is missing from photo, you can specify it manually in geo.json file def iter_photos() -> Iterator[Photo]: logger = get_logger() + for pp in PATHS: + assert os.path.lexists(pp) + geos: List[LatLon] = [] # stack of geos so we could use the most specific one # TODO could have this for all meta? e.g. time for d, _, files in itertools.chain.from_iterable((os.walk(pp) for pp in PATHS)): @@ -168,6 +217,10 @@ def iter_photos() -> Iterator[Photo]: for f in sorted(files): photo = join(d, f) + if ignore_path(photo): + logger.info(f"Ignoring {photo} due to regex") + continue + mtype = mime.from_file(photo) IGNORED = { diff --git a/photos/__main__.py b/photos/__main__.py index 04b4053..18502c4 100644 --- a/photos/__main__.py +++ b/photos/__main__.py @@ -19,6 +19,7 @@ if len(sys.argv) > 1: raise RuntimeError(f"Unknown command {cmd}") else: for p in iter_photos(): + print(f"{p.dt} {p.path} {p.tags}") pass # TODO need datetime! # print(p) diff --git a/run b/run new file mode 100755 index 0000000..e3c3878 --- /dev/null +++ b/run @@ -0,0 +1,3 @@ +#!/bin/bash +set -eu +python3 -m photos From ea55e3f665a37b9fb5f0f59122e74f7976f3be2a Mon Sep 17 00:00:00 2001 From: Dima Gerasimov Date: Mon, 22 Oct 2018 23:38:19 +0100 Subject: [PATCH 04/10] Change path to photos --- photos/__init__.py | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/photos/__init__.py b/photos/__init__.py index 80445a0..fba5e72 100644 --- a/photos/__init__.py +++ b/photos/__init__.py @@ -18,9 +18,17 @@ import logging def get_logger(): return logging.getLogger('photo-provider') +PATHS = [ + "***REMOVED***", +] +# TODO could use other pathes I suppose? +# TODO however then won't be accessible from dropbox + +# PATH = "***REMOVED***/***REMOVED***" +# PATH = "***REMOVED***/***REMOVED***" + +CACHE_PATH = "***REMOVED***" -geolocator = Nominatim() # TODO does it cache?? -mime = magic.Magic(mime=True) # TODO hmm, instead geo could be a dynamic property... although a bit wasteful @@ -51,20 +59,7 @@ def dt_from_path(p: str) -> Optional[datetime]: dates = mm.group(1) + mm.group(2) return datetime.strptime(dates, "%Y%m%d%H%M%S") -PATHS = [ - "***REMOVED***", - "***REMOVED***", - "***REMOVED***", -] -# TODO could use other pathes I suppose? -# TODO or maybe just use symlinks -# TODO however then won't be accessible from dropbox - -# PATH = "***REMOVED***/***REMOVED***" -# PATH = "***REMOVED***/***REMOVED***" - -CACHE_PATH = "***REMOVED***" - +# TODO ignore hidden dirs? LatLon = Tuple[float, float] # TODO PIL.ExifTags.TAGS @@ -194,12 +189,15 @@ def _try_photo(photo: str, mtype: str, dgeo: Optional[LatLon]) -> Optional[Photo def iter_photos() -> Iterator[Photo]: logger = get_logger() + geolocator = Nominatim() # TODO does it cache?? + mime = magic.Magic(mime=True) + for pp in PATHS: assert os.path.lexists(pp) geos: List[LatLon] = [] # stack of geos so we could use the most specific one # TODO could have this for all meta? e.g. time - for d, _, files in itertools.chain.from_iterable((os.walk(pp) for pp in PATHS)): + for d, _, files in itertools.chain.from_iterable((os.walk(pp, followlinks=True) for pp in PATHS)): logger.info(f"Processing {d}") geof = join(d, 'geo.json') @@ -244,7 +242,7 @@ def iter_photos() -> Iterator[Photo]: if cgeo is not None: geos.pop() -def get_photos(cached: bool=False) -> Iterable[Photo]: +def get_photos(cached: bool=False) -> List[Photo]: import dill # type: ignore if cached: with open(CACHE_PATH, 'rb') as fo: From 03c244ad507fc9bd98db3bb8737059e0c87a6faa Mon Sep 17 00:00:00 2001 From: Dima Gerasimov Date: Sat, 10 Nov 2018 16:15:10 +0000 Subject: [PATCH 05/10] add url support --- photos/__init__.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/photos/__init__.py b/photos/__init__.py index fba5e72..d2f920b 100644 --- a/photos/__init__.py +++ b/photos/__init__.py @@ -21,6 +21,10 @@ def get_logger(): PATHS = [ "***REMOVED***", ] + +PHOTOS_URL = "***REMOVED***" + + # TODO could use other pathes I suppose? # TODO however then won't be accessible from dropbox @@ -124,6 +128,22 @@ class Photo(NamedTuple): def tags(self) -> List[str]: # TODO return [] + @property + def _basename(self) -> str: + for bp in PATHS: + if self.path.startswith(bp): + return self.path[len(bp):] + else: + raise RuntimeError(f'Weird path {self.path}, cant match against anything') + + @property + def linkname(self) -> str: + return self._basename.strip('/') + + @property + def url(self) -> str: + return PHOTOS_URL + self._basename + def _try_photo(photo: str, mtype: str, dgeo: Optional[LatLon]) -> Optional[Photo]: logger = get_logger() From 5dc4f309fd9ed6962699773ab5ccfe7daf4391f1 Mon Sep 17 00:00:00 2001 From: Dima Gerasimov Date: Mon, 1 Apr 2019 18:34:34 +0100 Subject: [PATCH 07/10] kython, remove old ci script --- ci.sh | 10 ---------- photos/__init__.py | 5 ++--- photos/__main__.py | 2 +- 3 files changed, 3 insertions(+), 14 deletions(-) delete mode 100755 ci.sh diff --git a/ci.sh b/ci.sh deleted file mode 100755 index 0daaac3..0000000 --- a/ci.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -cd "$(this_dir)" || exit - -. ~/bash_ci - -ci_run mypy photos -ci_run pylint -E photos - -ci_report_errors diff --git a/photos/__init__.py b/photos/__init__.py index d2f920b..7bdb5bc 100644 --- a/photos/__init__.py +++ b/photos/__init__.py @@ -2,6 +2,7 @@ from datetime import datetime import itertools import os from os.path import join, basename +import json import re from typing import Tuple, Dict, Optional, NamedTuple, Iterator, Iterable, List @@ -12,8 +13,6 @@ import magic # type: ignore import PIL.Image # type: ignore from PIL.ExifTags import TAGS, GPSTAGS # type: ignore -from kython import json_load - import logging def get_logger(): return logging.getLogger('photo-provider') @@ -225,7 +224,7 @@ def iter_photos() -> Iterator[Photo]: if os.path.isfile(geof): j: Dict with open(geof, 'r') as fo: - j = json_load(fo) + j = json.load(fo) if 'name' in j: g = geolocator.geocode(j['name']) geo = (g.latitude, g.longitude) diff --git a/photos/__main__.py b/photos/__main__.py index 18502c4..09480ba 100644 --- a/photos/__main__.py +++ b/photos/__main__.py @@ -1,7 +1,7 @@ import logging logging.basicConfig(level=logging.INFO) -from kython.logging import setup_logzero +from kython.klogging import setup_logzero from photos import get_photos, iter_photos, get_logger From c35a4acb7dceffaeeedd56a16ed4eedf0c26dbee Mon Sep 17 00:00:00 2001 From: Dima Gerasimov Date: Wed, 3 Apr 2019 22:57:20 +0100 Subject: [PATCH 08/10] ignore timestamps far in future --- photos/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/photos/__init__.py b/photos/__init__.py index 7bdb5bc..54f7604 100644 --- a/photos/__init__.py +++ b/photos/__init__.py @@ -180,11 +180,17 @@ def _try_photo(photo: str, mtype: str, dgeo: Optional[LatLon]) -> Optional[Photo lon = convert(meta[LON], meta[LON_REF]) geo = (lat, lon) if dt is None: + # TODO eh. perhaps ignore all of instagram videos? they are also too behind in past... try: - dt = dt_from_path(photo) # ok, last try.. + edt = dt_from_path(photo) # ok, last try.. except Exception as e: logger.error(f"Error while trying to extract date from name {photo}") logger.exception(e) + else: + if edt is not None and edt > datetime.now(): + logger.error('datetime for %s is too far in future: %s', photo, edt) + else: + dt = edt return Photo(photo, dt, geo) From e8b18b6a74b61173d20fb0d9990548f48058fe5f Mon Sep 17 00:00:00 2001 From: Dima Gerasimov Date: Wed, 3 Apr 2019 22:59:37 +0100 Subject: [PATCH 09/10] ignore timestamp extraction for instagram videos --- photos/__init__.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/photos/__init__.py b/photos/__init__.py index 54f7604..65ccee3 100644 --- a/photos/__init__.py +++ b/photos/__init__.py @@ -180,17 +180,19 @@ def _try_photo(photo: str, mtype: str, dgeo: Optional[LatLon]) -> Optional[Photo lon = convert(meta[LON], meta[LON_REF]) geo = (lat, lon) if dt is None: - # TODO eh. perhaps ignore all of instagram videos? they are also too behind in past... - try: - edt = dt_from_path(photo) # ok, last try.. - except Exception as e: - logger.error(f"Error while trying to extract date from name {photo}") - logger.exception(e) + if 'Instagram/VID_' in photo: + logger.warning('ignoring timestamp extraction for %s, they are stupid for Instagram videos', photo) else: - if edt is not None and edt > datetime.now(): - logger.error('datetime for %s is too far in future: %s', photo, edt) + try: + edt = dt_from_path(photo) # ok, last try.. + except Exception as e: + logger.error(f"Error while trying to extract date from name {photo}") + logger.exception(e) else: - dt = edt + if edt is not None and edt > datetime.now(): + logger.error('datetime for %s is too far in future: %s', photo, edt) + else: + dt = edt return Photo(photo, dt, geo) From 29e70967f9833c1dc5c3035dbfa091b418d68237 Mon Sep 17 00:00:00 2001 From: Dima Gerasimov Date: Wed, 24 Apr 2019 18:55:52 +0100 Subject: [PATCH 10/10] add main --- photos/__main__.py | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/photos/__main__.py b/photos/__main__.py index 09480ba..3bbced2 100644 --- a/photos/__main__.py +++ b/photos/__main__.py @@ -1,25 +1,32 @@ import logging +# TODO eh? logging.basicConfig(level=logging.INFO) from kython.klogging import setup_logzero from photos import get_photos, iter_photos, get_logger -setup_logzero(get_logger(), level=logging.DEBUG) - import sys -if len(sys.argv) > 1: - cmd = sys.argv[1] - if cmd == "update_cache": - from photos import update_cache, get_photos - update_cache() - get_photos(cached=True) + +def main(): + setup_logzero(get_logger(), level=logging.DEBUG) + + if len(sys.argv) > 1: + cmd = sys.argv[1] + if cmd == "update_cache": + from photos import update_cache, get_photos + update_cache() + get_photos(cached=True) + else: + raise RuntimeError(f"Unknown command {cmd}") else: - raise RuntimeError(f"Unknown command {cmd}") -else: - for p in iter_photos(): - print(f"{p.dt} {p.path} {p.tags}") - pass - # TODO need datetime! - # print(p) + for p in iter_photos(): + print(f"{p.dt} {p.path} {p.tags}") + pass + # TODO need datetime! + # print(p) + + +if __name__ == '__main__': + main()