geopic-tag-reader 1.0.6__py3-none-any.whl → 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- geopic_tag_reader/__init__.py +1 -1
- geopic_tag_reader/main.py +1 -1
- geopic_tag_reader/reader.py +51 -12
- geopic_tag_reader/writer.py +2 -2
- {geopic_tag_reader-1.0.6.dist-info → geopic_tag_reader-1.1.0.dist-info}/METADATA +4 -4
- geopic_tag_reader-1.1.0.dist-info/RECORD +12 -0
- geopic_tag_reader-1.0.6.dist-info/RECORD +0 -12
- {geopic_tag_reader-1.0.6.dist-info → geopic_tag_reader-1.1.0.dist-info}/LICENSE +0 -0
- {geopic_tag_reader-1.0.6.dist-info → geopic_tag_reader-1.1.0.dist-info}/WHEEL +0 -0
- {geopic_tag_reader-1.0.6.dist-info → geopic_tag_reader-1.1.0.dist-info}/entry_points.txt +0 -0
geopic_tag_reader/__init__.py
CHANGED
geopic_tag_reader/main.py
CHANGED
|
@@ -22,7 +22,7 @@ def read(
|
|
|
22
22
|
|
|
23
23
|
print("Latitude:", metadata.lat)
|
|
24
24
|
print("Longitude:", metadata.lon)
|
|
25
|
-
print("Timestamp:", metadata.ts)
|
|
25
|
+
print("Timestamp:", metadata.ts.isoformat())
|
|
26
26
|
print("Heading:", metadata.heading)
|
|
27
27
|
print("Type:", metadata.type)
|
|
28
28
|
print("Make:", metadata.make)
|
geopic_tag_reader/reader.py
CHANGED
|
@@ -7,11 +7,15 @@ import re
|
|
|
7
7
|
import json
|
|
8
8
|
from fractions import Fraction
|
|
9
9
|
from geopic_tag_reader import camera
|
|
10
|
+
import timezonefinder # type: ignore
|
|
11
|
+
import pytz
|
|
10
12
|
|
|
11
13
|
# This is a fix for invalid MakerNotes leading to picture not read at all
|
|
12
14
|
# https://github.com/LeoHsiao1/pyexiv2/issues/58
|
|
13
15
|
pyexiv2.set_log_level(4)
|
|
14
16
|
|
|
17
|
+
tz_finder = timezonefinder.TimezoneFinder()
|
|
18
|
+
|
|
15
19
|
|
|
16
20
|
@dataclass
|
|
17
21
|
class CropValues:
|
|
@@ -41,7 +45,7 @@ class GeoPicTags:
|
|
|
41
45
|
Attributes:
|
|
42
46
|
lat (float): GPS Latitude (in WGS84)
|
|
43
47
|
lon (float): GPS Longitude (in WGS84)
|
|
44
|
-
ts (
|
|
48
|
+
ts (datetime): The capture date (date & time with timezone)
|
|
45
49
|
heading (int): Picture heading (in degrees, North = 0°, East = 90°, South = 180°, West = 270°)
|
|
46
50
|
type (str): The kind of picture (flat, equirectangular)
|
|
47
51
|
make (str): The camera manufacturer name
|
|
@@ -58,7 +62,7 @@ class GeoPicTags:
|
|
|
58
62
|
|
|
59
63
|
lat: float
|
|
60
64
|
lon: float
|
|
61
|
-
ts:
|
|
65
|
+
ts: datetime.datetime
|
|
62
66
|
heading: Optional[int]
|
|
63
67
|
type: str
|
|
64
68
|
make: Optional[str]
|
|
@@ -86,7 +90,7 @@ class PartialGeoPicTags:
|
|
|
86
90
|
|
|
87
91
|
lat: Optional[float] = None
|
|
88
92
|
lon: Optional[float] = None
|
|
89
|
-
ts: Optional[
|
|
93
|
+
ts: Optional[datetime.datetime] = None
|
|
90
94
|
heading: Optional[int] = None
|
|
91
95
|
type: Optional[str] = None
|
|
92
96
|
make: Optional[str] = None
|
|
@@ -142,6 +146,11 @@ def readPictureMetadata(picture: bytes) -> GeoPicTags:
|
|
|
142
146
|
except:
|
|
143
147
|
pass
|
|
144
148
|
|
|
149
|
+
# Sanitize charset information
|
|
150
|
+
for k, v in data.items():
|
|
151
|
+
if isinstance(v, str):
|
|
152
|
+
data[k] = re.sub(r"charset=[^\s]+", "", v).strip()
|
|
153
|
+
|
|
145
154
|
# Parse latitude/longitude
|
|
146
155
|
lat, lon, llw = decodeLatLon(data, "Exif.GPSInfo")
|
|
147
156
|
if len(llw) > 0:
|
|
@@ -163,13 +172,13 @@ def readPictureMetadata(picture: bytes) -> GeoPicTags:
|
|
|
163
172
|
raise InvalidExifException("Read longitude is out of WGS84 bounds (should be in [-180, 180])")
|
|
164
173
|
|
|
165
174
|
# Parse date/time
|
|
166
|
-
d, llw = decodeGPSDateTime(data, "Exif.GPSInfo")
|
|
175
|
+
d, llw = decodeGPSDateTime(data, "Exif.GPSInfo", lat, lon)
|
|
167
176
|
|
|
168
177
|
if len(llw) > 0:
|
|
169
178
|
warnings.extend(llw)
|
|
170
179
|
|
|
171
180
|
if d is None:
|
|
172
|
-
d, llw = decodeGPSDateTime(data, "Xmp.exif")
|
|
181
|
+
d, llw = decodeGPSDateTime(data, "Xmp.exif", lat, lon)
|
|
173
182
|
if len(llw) > 0:
|
|
174
183
|
warnings.extend(llw)
|
|
175
184
|
|
|
@@ -180,7 +189,7 @@ def readPictureMetadata(picture: bytes) -> GeoPicTags:
|
|
|
180
189
|
"Xmp.GPano.SourceImageCreateTime",
|
|
181
190
|
]:
|
|
182
191
|
if d is None:
|
|
183
|
-
d, llw = decodeDateTimeOriginal(data, exifField)
|
|
192
|
+
d, llw = decodeDateTimeOriginal(data, exifField, lat, lon)
|
|
184
193
|
if len(llw) > 0:
|
|
185
194
|
warnings.extend(llw)
|
|
186
195
|
|
|
@@ -313,7 +322,7 @@ def readPictureMetadata(picture: bytes) -> GeoPicTags:
|
|
|
313
322
|
PartialGeoPicTags(
|
|
314
323
|
lat,
|
|
315
324
|
lon,
|
|
316
|
-
d
|
|
325
|
+
d,
|
|
317
326
|
heading,
|
|
318
327
|
pic_type,
|
|
319
328
|
make,
|
|
@@ -330,7 +339,7 @@ def readPictureMetadata(picture: bytes) -> GeoPicTags:
|
|
|
330
339
|
return GeoPicTags(
|
|
331
340
|
lat,
|
|
332
341
|
lon,
|
|
333
|
-
d
|
|
342
|
+
d,
|
|
334
343
|
heading,
|
|
335
344
|
pic_type,
|
|
336
345
|
make,
|
|
@@ -429,7 +438,9 @@ def decodeLatLon(data: dict, group: str) -> Tuple[Optional[float], Optional[floa
|
|
|
429
438
|
return (lat, lon, warnings)
|
|
430
439
|
|
|
431
440
|
|
|
432
|
-
def decodeDateTimeOriginal(
|
|
441
|
+
def decodeDateTimeOriginal(
|
|
442
|
+
data: dict, datetimeField: str, lat: Optional[float] = None, lon: Optional[float] = None
|
|
443
|
+
) -> Tuple[Optional[datetime.datetime], List[str]]:
|
|
433
444
|
d = None
|
|
434
445
|
warnings = []
|
|
435
446
|
|
|
@@ -442,7 +453,6 @@ def decodeDateTimeOriginal(data: dict, datetimeField: str) -> Tuple[Optional[dat
|
|
|
442
453
|
secondsRaw, microsecondsRaw, msw = decodeSecondsAndMicroSeconds(
|
|
443
454
|
timeRaw[2], data["Exif.Photo.SubSecTimeOriginal"] if isExifTagUsable(data, "Exif.Photo.SubSecTimeOriginal", float) else "0"
|
|
444
455
|
)
|
|
445
|
-
tz = decodeTimeOffset(data, f"Exif.Photo.OffsetTime{'Original' if 'DateTimeOriginal' in datetimeField else ''}")
|
|
446
456
|
warnings += msw
|
|
447
457
|
|
|
448
458
|
d = datetime.datetime.combine(
|
|
@@ -452,9 +462,30 @@ def decodeDateTimeOriginal(data: dict, datetimeField: str) -> Tuple[Optional[dat
|
|
|
452
462
|
minutesRaw,
|
|
453
463
|
secondsRaw,
|
|
454
464
|
microsecondsRaw,
|
|
455
|
-
tzinfo=tz or datetime.timezone.utc,
|
|
456
465
|
),
|
|
457
466
|
)
|
|
467
|
+
|
|
468
|
+
# Timezone handling
|
|
469
|
+
# Try to read from EXIF
|
|
470
|
+
tz = decodeTimeOffset(data, f"Exif.Photo.OffsetTime{'Original' if 'DateTimeOriginal' in datetimeField else ''}")
|
|
471
|
+
if tz is not None:
|
|
472
|
+
d = d.replace(tzinfo=tz)
|
|
473
|
+
|
|
474
|
+
# Otherwise, try to deduct from coordinates
|
|
475
|
+
elif lon is not None and lat is not None:
|
|
476
|
+
tz_name = tz_finder.timezone_at(lng=lon, lat=lat)
|
|
477
|
+
if tz_name is not None:
|
|
478
|
+
d = pytz.timezone(tz_name).localize(d)
|
|
479
|
+
# Otherwise, default to UTC + warning
|
|
480
|
+
else:
|
|
481
|
+
d = d.replace(tzinfo=datetime.timezone.utc)
|
|
482
|
+
warnings.append("Precise timezone information not found, fallback to UTC")
|
|
483
|
+
|
|
484
|
+
# Otherwise, default to UTC + warning
|
|
485
|
+
else:
|
|
486
|
+
d = d.replace(tzinfo=datetime.timezone.utc)
|
|
487
|
+
warnings.append("Precise timezone information not found (and no GPS coordinates to help), fallback to UTC")
|
|
488
|
+
|
|
458
489
|
except ValueError as e:
|
|
459
490
|
warnings.append("Skipping original date/time (from " + datetimeField + ") as it was not recognized:\n\t" + str(e))
|
|
460
491
|
|
|
@@ -467,7 +498,9 @@ def decodeTimeOffset(data: dict, offsetTimeField: str) -> Optional[datetime.tzin
|
|
|
467
498
|
return None
|
|
468
499
|
|
|
469
500
|
|
|
470
|
-
def decodeGPSDateTime(
|
|
501
|
+
def decodeGPSDateTime(
|
|
502
|
+
data: dict, group: str, lat: Optional[float] = None, lon: Optional[float] = None
|
|
503
|
+
) -> Tuple[Optional[datetime.datetime], List[str]]:
|
|
471
504
|
d = None
|
|
472
505
|
warnings = []
|
|
473
506
|
|
|
@@ -504,6 +537,12 @@ def decodeGPSDateTime(data: dict, group: str) -> Tuple[Optional[datetime.datetim
|
|
|
504
537
|
),
|
|
505
538
|
)
|
|
506
539
|
|
|
540
|
+
# Set timezone from coordinates
|
|
541
|
+
if lon is not None and lat is not None:
|
|
542
|
+
tz_name = tz_finder.timezone_at(lng=lon, lat=lat)
|
|
543
|
+
if tz_name is not None:
|
|
544
|
+
d = d.astimezone(pytz.timezone(tz_name))
|
|
545
|
+
|
|
507
546
|
except ValueError as e:
|
|
508
547
|
warnings.append(f"Skipping GPS date/time ({group} group) as it was not recognized:\n\t{str(e)}")
|
|
509
548
|
|
geopic_tag_reader/writer.py
CHANGED
|
@@ -3,11 +3,11 @@ from datetime import datetime, timedelta
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from geopic_tag_reader.model import PictureType
|
|
5
5
|
from enum import Enum
|
|
6
|
+
import timezonefinder # type: ignore
|
|
7
|
+
import pytz
|
|
6
8
|
|
|
7
9
|
try:
|
|
8
10
|
import pyexiv2 # type: ignore
|
|
9
|
-
import timezonefinder # type: ignore
|
|
10
|
-
import pytz
|
|
11
11
|
except ImportError:
|
|
12
12
|
raise Exception(
|
|
13
13
|
"""Impossible to write the exif tags without the '[write-exif]' dependency (that will need to install libexiv2).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: geopic-tag-reader
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: GeoPicTagReader
|
|
5
5
|
Author-email: Adrien PAVIE <panieravide@riseup.net>
|
|
6
6
|
Requires-Python: >=3.8
|
|
@@ -9,6 +9,9 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
9
9
|
Requires-Dist: typer ~= 0.12
|
|
10
10
|
Requires-Dist: xmltodict ~= 0.13
|
|
11
11
|
Requires-Dist: pyexiv2 == 2.8.3
|
|
12
|
+
Requires-Dist: timezonefinder == 6.2.0
|
|
13
|
+
Requires-Dist: pytz ~= 2023.3
|
|
14
|
+
Requires-Dist: types-pytz ~= 2023.3.0.1
|
|
12
15
|
Requires-Dist: flit ~= 3.8.0 ; extra == "build"
|
|
13
16
|
Requires-Dist: black ~= 24.3 ; extra == "dev"
|
|
14
17
|
Requires-Dist: mypy ~= 1.9 ; extra == "dev"
|
|
@@ -17,9 +20,6 @@ Requires-Dist: pytest-datafiles ~= 3.0 ; extra == "dev"
|
|
|
17
20
|
Requires-Dist: lazydocs ~= 0.4.8 ; extra == "dev"
|
|
18
21
|
Requires-Dist: types-xmltodict ~= 0.13 ; extra == "dev"
|
|
19
22
|
Requires-Dist: pre-commit ~= 3.3.3 ; extra == "dev"
|
|
20
|
-
Requires-Dist: timezonefinder == 6.2.0 ; extra == "write-exif"
|
|
21
|
-
Requires-Dist: pytz ~= 2023.3 ; extra == "write-exif"
|
|
22
|
-
Requires-Dist: types-pytz ~= 2023.3.0.1 ; extra == "write-exif"
|
|
23
23
|
Requires-Dist: python-dateutil ~= 2.8.2 ; extra == "write-exif"
|
|
24
24
|
Project-URL: Home, https://gitlab.com/geovisio/geo-picture-tag-reader
|
|
25
25
|
Provides-Extra: build
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
geopic_tag_reader/__init__.py,sha256=o8d20yL96tGKyj_0NXyBn_HJ2i1hofM-qTuKouf4ZkE,47
|
|
2
|
+
geopic_tag_reader/camera.py,sha256=2Sr0jAt3RXUWazYMnkwF6J6lVnKvSp7Ac8g7yOHehVA,1643
|
|
3
|
+
geopic_tag_reader/main.py,sha256=a0RmJIA0R5dTK1vzDZD3Q4MFFAiDv6pnRiFHjfIjeJU,3141
|
|
4
|
+
geopic_tag_reader/model.py,sha256=rsWVE3T1kpNsKXX8iv6xb_3PCVY6Ea7iU9WOqUgXklU,129
|
|
5
|
+
geopic_tag_reader/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
geopic_tag_reader/reader.py,sha256=w19zJ9CvdJyk_UxWcYX2T0DkhdQmw2xMzlAo1sR1ir0,22867
|
|
7
|
+
geopic_tag_reader/writer.py,sha256=QmQqQpWgb6AL3Y0Kuzy7PF1asUsTVDq9buwJXfFSRq8,8672
|
|
8
|
+
geopic_tag_reader-1.1.0.dist-info/entry_points.txt,sha256=c9YwjCNhxveDf-61_aSRlzcpoutvM6KQCerlzaVt_JU,64
|
|
9
|
+
geopic_tag_reader-1.1.0.dist-info/LICENSE,sha256=oHWDwXkJJb9zJzThMN3F9Li4yFhz1qxOUByouY7L3bI,1070
|
|
10
|
+
geopic_tag_reader-1.1.0.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
|
|
11
|
+
geopic_tag_reader-1.1.0.dist-info/METADATA,sha256=DJ9OiZyvVGNMdveGev-V33kO9sqPPo-V9SADIezd0I4,6303
|
|
12
|
+
geopic_tag_reader-1.1.0.dist-info/RECORD,,
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
geopic_tag_reader/__init__.py,sha256=AB9t1irfcb0A7j8MHsCnqxKwAll8VkwJKfgEAPE4Jw4,47
|
|
2
|
-
geopic_tag_reader/camera.py,sha256=2Sr0jAt3RXUWazYMnkwF6J6lVnKvSp7Ac8g7yOHehVA,1643
|
|
3
|
-
geopic_tag_reader/main.py,sha256=LohJW0xX0A8DAbcUR_BnJ2UvN4qo6yydzOGtq3zIArA,3129
|
|
4
|
-
geopic_tag_reader/model.py,sha256=rsWVE3T1kpNsKXX8iv6xb_3PCVY6Ea7iU9WOqUgXklU,129
|
|
5
|
-
geopic_tag_reader/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
geopic_tag_reader/reader.py,sha256=PK1mN7TFFC8V70-pU_EM6cTESNcAed1GydHWUkqkT3A,21325
|
|
7
|
-
geopic_tag_reader/writer.py,sha256=L13ewadZhZic13k66FsQvqGQ_s512nssKvvSwZp4Ggg,8680
|
|
8
|
-
geopic_tag_reader-1.0.6.dist-info/entry_points.txt,sha256=c9YwjCNhxveDf-61_aSRlzcpoutvM6KQCerlzaVt_JU,64
|
|
9
|
-
geopic_tag_reader-1.0.6.dist-info/LICENSE,sha256=oHWDwXkJJb9zJzThMN3F9Li4yFhz1qxOUByouY7L3bI,1070
|
|
10
|
-
geopic_tag_reader-1.0.6.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
|
|
11
|
-
geopic_tag_reader-1.0.6.dist-info/METADATA,sha256=US7A6UqzLzwlLvtCiHWPQszwLTNtNt_Pzka6DLJBbmg,6375
|
|
12
|
-
geopic_tag_reader-1.0.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|