geopic-tag-reader 1.1.1__py3-none-any.whl → 1.1.3__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/camera.py +14 -3
- geopic_tag_reader/i18n.py +11 -0
- geopic_tag_reader/main.py +18 -13
- geopic_tag_reader/reader.py +68 -38
- geopic_tag_reader/translations/fr/LC_MESSAGES/geopic_tag_reader.po +114 -0
- geopic_tag_reader/translations/geopic_tag_reader.pot +147 -0
- geopic_tag_reader/writer.py +6 -2
- {geopic_tag_reader-1.1.1.dist-info → geopic_tag_reader-1.1.3.dist-info}/METADATA +12 -39
- geopic_tag_reader-1.1.3.dist-info/RECORD +15 -0
- geopic_tag_reader-1.1.1.dist-info/RECORD +0 -12
- {geopic_tag_reader-1.1.1.dist-info → geopic_tag_reader-1.1.3.dist-info}/LICENSE +0 -0
- {geopic_tag_reader-1.1.1.dist-info → geopic_tag_reader-1.1.3.dist-info}/WHEEL +0 -0
- {geopic_tag_reader-1.1.1.dist-info → geopic_tag_reader-1.1.3.dist-info}/entry_points.txt +0 -0
geopic_tag_reader/__init__.py
CHANGED
geopic_tag_reader/camera.py
CHANGED
|
@@ -39,11 +39,22 @@ def is_360(make: Optional[str] = None, model: Optional[str] = None, width: Optio
|
|
|
39
39
|
True
|
|
40
40
|
>>> is_360("GoPro", "Max 360", "1024", "768")
|
|
41
41
|
False
|
|
42
|
+
>>> is_360("RICOH", "THETA S", "5376", "2688")
|
|
43
|
+
True
|
|
42
44
|
"""
|
|
43
45
|
|
|
46
|
+
# Check make and model are defined
|
|
44
47
|
if not make or not model:
|
|
45
48
|
return False
|
|
46
49
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
# Check width and height are equirectangular
|
|
51
|
+
if not ((width is None or height is None) or int(width) == 2 * int(height)):
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
# Find make
|
|
55
|
+
matchMake = next((m for m in EQUIRECTANGULAR_MODELS.keys() if make.lower() == m.lower()), None)
|
|
56
|
+
if matchMake is None:
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
# Find model
|
|
60
|
+
return any(model.lower().startswith(m.lower()) for m in EQUIRECTANGULAR_MODELS[matchMake])
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import gettext
|
|
2
|
+
import os
|
|
3
|
+
from typing import Callable
|
|
4
|
+
|
|
5
|
+
localedir = os.path.join(os.path.abspath(os.path.dirname(__file__)), "translations")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def init(lang_code: str = "en") -> Callable[[str], str]:
|
|
9
|
+
lang = gettext.translation("geopic_tag_reader", localedir, languages=[lang_code], fallback=True)
|
|
10
|
+
lang.install()
|
|
11
|
+
return lang.gettext
|
geopic_tag_reader/main.py
CHANGED
|
@@ -4,6 +4,7 @@ from geopic_tag_reader import reader
|
|
|
4
4
|
from geopic_tag_reader.model import PictureType
|
|
5
5
|
from typing import Optional
|
|
6
6
|
import pyexiv2 # type: ignore
|
|
7
|
+
from geopic_tag_reader.i18n import init as i18n_init
|
|
7
8
|
|
|
8
9
|
app = typer.Typer(help="GeoPicTagReader")
|
|
9
10
|
|
|
@@ -12,28 +13,30 @@ app = typer.Typer(help="GeoPicTagReader")
|
|
|
12
13
|
def read(
|
|
13
14
|
image: Path = typer.Option(..., help="Path to your JPEG image file"),
|
|
14
15
|
ignore_exiv2_errors: bool = typer.Option(False, "--ignore-exiv2-errors", help="Do not stop execution even if Exiv2 throws errors"),
|
|
16
|
+
lang: str = typer.Option("en", help="Lang code (2 letters) to use for printing messages"),
|
|
15
17
|
):
|
|
16
18
|
"""Reads EXIF metadata from a picture file, and prints results"""
|
|
17
19
|
|
|
18
20
|
with open(image, "rb") as img:
|
|
19
21
|
pyexiv2.set_log_level(4 if ignore_exiv2_errors else 2)
|
|
20
22
|
|
|
21
|
-
metadata = reader.readPictureMetadata(img.read())
|
|
23
|
+
metadata = reader.readPictureMetadata(img.read(), lang)
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
print("
|
|
25
|
-
print("
|
|
26
|
-
print("
|
|
27
|
-
print("
|
|
28
|
-
print("
|
|
29
|
-
print("
|
|
30
|
-
print("
|
|
31
|
-
print("
|
|
32
|
-
print("
|
|
33
|
-
print("
|
|
25
|
+
_ = i18n_init(lang)
|
|
26
|
+
print(_("Latitude:"), metadata.lat)
|
|
27
|
+
print(_("Longitude:"), metadata.lon)
|
|
28
|
+
print(_("Timestamp:"), metadata.ts.isoformat())
|
|
29
|
+
print(_("Heading:"), metadata.heading)
|
|
30
|
+
print(_("Type:"), metadata.type)
|
|
31
|
+
print(_("Make:"), metadata.make)
|
|
32
|
+
print(_("Model:"), metadata.model)
|
|
33
|
+
print(_("Focal length:"), metadata.focal_length)
|
|
34
|
+
print(_("Crop parameters:"), metadata.crop)
|
|
35
|
+
print(_("Pitch:"), metadata.pitch)
|
|
36
|
+
print(_("Roll:"), metadata.roll)
|
|
34
37
|
|
|
35
38
|
if len(metadata.tagreader_warnings) > 0:
|
|
36
|
-
print("Warnings raised by reader:")
|
|
39
|
+
print(_("Warnings raised by reader:"))
|
|
37
40
|
for w in metadata.tagreader_warnings:
|
|
38
41
|
print(" - " + w)
|
|
39
42
|
|
|
@@ -60,6 +63,7 @@ def write(
|
|
|
60
63
|
default=None,
|
|
61
64
|
help="type of picture, `equirectangular` for 360° pictures, `flat` otherwise",
|
|
62
65
|
),
|
|
66
|
+
lang: str = typer.Option("en", help="Lang code (2 letters) to use for printing messages"),
|
|
63
67
|
):
|
|
64
68
|
"""Override certain exiftags of a picture and write a new picture in another file"""
|
|
65
69
|
from geopic_tag_reader import writer
|
|
@@ -71,6 +75,7 @@ def write(
|
|
|
71
75
|
updated_pic = writer.writePictureMetadata(
|
|
72
76
|
raw_input.read(),
|
|
73
77
|
writer.PictureMetadata(capture_time=capture_dt, longitude=longitude, latitude=latitude, picture_type=picture_type),
|
|
78
|
+
lang,
|
|
74
79
|
)
|
|
75
80
|
|
|
76
81
|
out = output or input
|
geopic_tag_reader/reader.py
CHANGED
|
@@ -2,13 +2,14 @@ import xmltodict
|
|
|
2
2
|
import pyexiv2 # type: ignore
|
|
3
3
|
import datetime
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
|
-
from typing import Dict, List, Optional, Any, Set, Tuple
|
|
5
|
+
from typing import Dict, List, Optional, Any, Set, Tuple, Callable
|
|
6
6
|
import re
|
|
7
7
|
import json
|
|
8
8
|
from fractions import Fraction
|
|
9
9
|
from geopic_tag_reader import camera
|
|
10
10
|
import timezonefinder # type: ignore
|
|
11
11
|
import pytz
|
|
12
|
+
from geopic_tag_reader.i18n import init as i18n_init
|
|
12
13
|
|
|
13
14
|
# This is a fix for invalid MakerNotes leading to picture not read at all
|
|
14
15
|
# https://github.com/LeoHsiao1/pyexiv2/issues/58
|
|
@@ -85,6 +86,10 @@ class InvalidExifException(Exception):
|
|
|
85
86
|
super().__init__(msg)
|
|
86
87
|
|
|
87
88
|
|
|
89
|
+
class InvalidFractionException(Exception):
|
|
90
|
+
"""Exception for invalid list of fractions"""
|
|
91
|
+
|
|
92
|
+
|
|
88
93
|
@dataclass
|
|
89
94
|
class PartialGeoPicTags:
|
|
90
95
|
"""Tags associated to a geolocated picture when not all tags have been found
|
|
@@ -121,16 +126,18 @@ class PartialExifException(Exception):
|
|
|
121
126
|
self.tags = partial_tags
|
|
122
127
|
|
|
123
128
|
|
|
124
|
-
def readPictureMetadata(picture: bytes) -> GeoPicTags:
|
|
129
|
+
def readPictureMetadata(picture: bytes, lang_code: str = "en") -> GeoPicTags:
|
|
125
130
|
"""Extracts metadata from picture file
|
|
126
131
|
|
|
127
132
|
Args:
|
|
128
133
|
picture (bytes): Picture file
|
|
134
|
+
lang_code (str): Language code for translating error labels
|
|
129
135
|
|
|
130
136
|
Returns:
|
|
131
137
|
GeoPicTags: Extracted metadata from picture
|
|
132
138
|
"""
|
|
133
139
|
|
|
140
|
+
_ = i18n_init(lang_code)
|
|
134
141
|
warnings = []
|
|
135
142
|
img = pyexiv2.ImageData(picture)
|
|
136
143
|
data = {}
|
|
@@ -158,12 +165,12 @@ def readPictureMetadata(picture: bytes) -> GeoPicTags:
|
|
|
158
165
|
data[k] = re.sub(r"charset=[^\s]+", "", v).strip()
|
|
159
166
|
|
|
160
167
|
# Parse latitude/longitude
|
|
161
|
-
lat, lon, llw = decodeLatLon(data, "Exif.GPSInfo")
|
|
168
|
+
lat, lon, llw = decodeLatLon(data, "Exif.GPSInfo", _)
|
|
162
169
|
if len(llw) > 0:
|
|
163
170
|
warnings.extend(llw)
|
|
164
171
|
|
|
165
172
|
if lat is None:
|
|
166
|
-
lat, lon, llw = decodeLatLon(data, "Xmp.exif")
|
|
173
|
+
lat, lon, llw = decodeLatLon(data, "Xmp.exif", _)
|
|
167
174
|
if len(llw) > 0:
|
|
168
175
|
warnings.extend(llw)
|
|
169
176
|
|
|
@@ -173,18 +180,18 @@ def readPictureMetadata(picture: bytes) -> GeoPicTags:
|
|
|
173
180
|
|
|
174
181
|
# Check coordinates validity
|
|
175
182
|
if lat is not None and (lat < -90 or lat > 90):
|
|
176
|
-
raise InvalidExifException("Read latitude is out of WGS84 bounds (should be in [-90, 90])")
|
|
183
|
+
raise InvalidExifException(_("Read latitude is out of WGS84 bounds (should be in [-90, 90])"))
|
|
177
184
|
if lon is not None and (lon < -180 or lon > 180):
|
|
178
|
-
raise InvalidExifException("Read longitude is out of WGS84 bounds (should be in [-180, 180])")
|
|
185
|
+
raise InvalidExifException(_("Read longitude is out of WGS84 bounds (should be in [-180, 180])"))
|
|
179
186
|
|
|
180
187
|
# Parse date/time
|
|
181
|
-
d, llw = decodeGPSDateTime(data, "Exif.GPSInfo", lat, lon)
|
|
188
|
+
d, llw = decodeGPSDateTime(data, "Exif.GPSInfo", _, lat, lon)
|
|
182
189
|
|
|
183
190
|
if len(llw) > 0:
|
|
184
191
|
warnings.extend(llw)
|
|
185
192
|
|
|
186
193
|
if d is None:
|
|
187
|
-
d, llw = decodeGPSDateTime(data, "Xmp.exif", lat, lon)
|
|
194
|
+
d, llw = decodeGPSDateTime(data, "Xmp.exif", _, lat, lon)
|
|
188
195
|
if len(llw) > 0:
|
|
189
196
|
warnings.extend(llw)
|
|
190
197
|
|
|
@@ -195,7 +202,7 @@ def readPictureMetadata(picture: bytes) -> GeoPicTags:
|
|
|
195
202
|
"Xmp.GPano.SourceImageCreateTime",
|
|
196
203
|
]:
|
|
197
204
|
if d is None:
|
|
198
|
-
d, llw = decodeDateTimeOriginal(data, exifField, lat, lon)
|
|
205
|
+
d, llw = decodeDateTimeOriginal(data, exifField, _, lat, lon)
|
|
199
206
|
if len(llw) > 0:
|
|
200
207
|
warnings.extend(llw)
|
|
201
208
|
|
|
@@ -217,7 +224,7 @@ def readPictureMetadata(picture: bytes) -> GeoPicTags:
|
|
|
217
224
|
)
|
|
218
225
|
|
|
219
226
|
except Exception as e:
|
|
220
|
-
warnings.append("Skipping Mapillary date/time as it was not recognized
|
|
227
|
+
warnings.append(_("Skipping Mapillary date/time as it was not recognized: {v}").format(v=data["MAPGpsTime"]))
|
|
221
228
|
|
|
222
229
|
# Heading/Yaw
|
|
223
230
|
heading = None
|
|
@@ -230,7 +237,7 @@ def readPictureMetadata(picture: bytes) -> GeoPicTags:
|
|
|
230
237
|
heading = gpanoHeading
|
|
231
238
|
else:
|
|
232
239
|
if gpsDir != gpanoHeading:
|
|
233
|
-
warnings.append("Contradicting heading values found, GPSImgDirection value is used")
|
|
240
|
+
warnings.append(_("Contradicting heading values found, GPSImgDirection value is used"))
|
|
234
241
|
heading = gpsDir
|
|
235
242
|
|
|
236
243
|
elif isExifTagUsable(data, "Xmp.GPano.PoseHeadingDegrees", float):
|
|
@@ -341,18 +348,25 @@ def readPictureMetadata(picture: bytes) -> GeoPicTags:
|
|
|
341
348
|
errors = []
|
|
342
349
|
missing_fields = set()
|
|
343
350
|
if not lat or not lon:
|
|
344
|
-
errors.append("No GPS coordinates or broken coordinates in picture EXIF tags")
|
|
351
|
+
errors.append(_("No GPS coordinates or broken coordinates in picture EXIF tags"))
|
|
345
352
|
if not lat:
|
|
346
353
|
missing_fields.add("lat")
|
|
347
354
|
if not lon:
|
|
348
355
|
missing_fields.add("lon")
|
|
349
356
|
if d is None:
|
|
350
|
-
errors.append("No valid date in picture EXIF tags")
|
|
357
|
+
errors.append(_("No valid date in picture EXIF tags"))
|
|
351
358
|
missing_fields.add("datetime")
|
|
352
359
|
|
|
353
360
|
if errors:
|
|
361
|
+
if len(errors) > 1:
|
|
362
|
+
listOfErrors = _("The picture is missing mandatory metadata:")
|
|
363
|
+
errorSep = "\n\t- "
|
|
364
|
+
listOfErrors += errorSep + errorSep.join(errors)
|
|
365
|
+
else:
|
|
366
|
+
listOfErrors = errors[0]
|
|
367
|
+
|
|
354
368
|
raise PartialExifException(
|
|
355
|
-
|
|
369
|
+
listOfErrors,
|
|
356
370
|
missing_fields,
|
|
357
371
|
PartialGeoPicTags(
|
|
358
372
|
lat,
|
|
@@ -415,14 +429,14 @@ def decodeManyFractions(value: str) -> List[Fraction]:
|
|
|
415
429
|
try:
|
|
416
430
|
vals = [Fraction(v.strip()) for v in value.split(" ")]
|
|
417
431
|
if len([True for v in vals if v.denominator == 0]) > 0:
|
|
418
|
-
raise
|
|
432
|
+
raise InvalidFractionException()
|
|
419
433
|
return vals
|
|
420
434
|
|
|
421
435
|
except:
|
|
422
|
-
raise
|
|
436
|
+
raise InvalidFractionException()
|
|
423
437
|
|
|
424
438
|
|
|
425
|
-
def decodeLatLon(data: dict, group: str) -> Tuple[Optional[float], Optional[float], List[str]]:
|
|
439
|
+
def decodeLatLon(data: dict, group: str, _: Callable[[str], str]) -> Tuple[Optional[float], Optional[float], List[str]]:
|
|
426
440
|
"""Reads GPS info from given group to get latitude/longitude as float coordinates"""
|
|
427
441
|
|
|
428
442
|
lat, lon = None, None
|
|
@@ -432,7 +446,7 @@ def decodeLatLon(data: dict, group: str) -> Tuple[Optional[float], Optional[floa
|
|
|
432
446
|
latRaw = decodeManyFractions(data[f"{group}.GPSLatitude"])
|
|
433
447
|
if len(latRaw) == 3:
|
|
434
448
|
if not isExifTagUsable(data, f"{group}.GPSLatitudeRef"):
|
|
435
|
-
warnings.append("GPSLatitudeRef not found, assuming GPSLatitudeRef is North")
|
|
449
|
+
warnings.append(_("GPSLatitudeRef not found, assuming GPSLatitudeRef is North"))
|
|
436
450
|
latRef = 1
|
|
437
451
|
else:
|
|
438
452
|
latRef = -1 if data[f"{group}.GPSLatitudeRef"].startswith("S") else 1
|
|
@@ -440,10 +454,10 @@ def decodeLatLon(data: dict, group: str) -> Tuple[Optional[float], Optional[floa
|
|
|
440
454
|
|
|
441
455
|
lonRaw = decodeManyFractions(data[f"{group}.GPSLongitude"])
|
|
442
456
|
if len(lonRaw) != 3:
|
|
443
|
-
raise InvalidExifException("Broken GPS coordinates in picture EXIF tags")
|
|
457
|
+
raise InvalidExifException(_("Broken GPS coordinates in picture EXIF tags"))
|
|
444
458
|
|
|
445
459
|
if not isExifTagUsable(data, f"{group}.GPSLongitudeRef"):
|
|
446
|
-
warnings.append("GPSLongitudeRef not found, assuming GPSLongitudeRef is East")
|
|
460
|
+
warnings.append(_("GPSLongitudeRef not found, assuming GPSLongitudeRef is East"))
|
|
447
461
|
lonRef = 1
|
|
448
462
|
else:
|
|
449
463
|
lonRef = -1 if data[f"{group}.GPSLongitudeRef"].startswith("W") else 1
|
|
@@ -461,13 +475,13 @@ def decodeLatLon(data: dict, group: str) -> Tuple[Optional[float], Optional[floa
|
|
|
461
475
|
if rawLat and rawLon:
|
|
462
476
|
latRef = 1
|
|
463
477
|
if not isExifTagUsable(data, f"{group}.GPSLatitudeRef"):
|
|
464
|
-
warnings.append("GPSLatitudeRef not found, assuming GPSLatitudeRef is North")
|
|
478
|
+
warnings.append(_("GPSLatitudeRef not found, assuming GPSLatitudeRef is North"))
|
|
465
479
|
else:
|
|
466
480
|
latRef = -1 if data[f"{group}.GPSLatitudeRef"].startswith("S") else 1
|
|
467
481
|
|
|
468
482
|
lonRef = 1
|
|
469
483
|
if not isExifTagUsable(data, f"{group}.GPSLongitudeRef"):
|
|
470
|
-
warnings.append("GPSLongitudeRef not found, assuming GPSLongitudeRef is East")
|
|
484
|
+
warnings.append(_("GPSLongitudeRef not found, assuming GPSLongitudeRef is East"))
|
|
471
485
|
else:
|
|
472
486
|
lonRef = -1 if data[f"{group}.GPSLongitudeRef"].startswith("W") else 1
|
|
473
487
|
|
|
@@ -478,7 +492,7 @@ def decodeLatLon(data: dict, group: str) -> Tuple[Optional[float], Optional[floa
|
|
|
478
492
|
|
|
479
493
|
|
|
480
494
|
def decodeDateTimeOriginal(
|
|
481
|
-
data: dict, datetimeField: str, lat: Optional[float] = None, lon: Optional[float] = None
|
|
495
|
+
data: dict, datetimeField: str, _: Callable[[str], str], lat: Optional[float] = None, lon: Optional[float] = None
|
|
482
496
|
) -> Tuple[Optional[datetime.datetime], List[str]]:
|
|
483
497
|
d = None
|
|
484
498
|
warnings = []
|
|
@@ -490,7 +504,9 @@ def decodeDateTimeOriginal(
|
|
|
490
504
|
hourRaw = int(timeRaw[0])
|
|
491
505
|
minutesRaw = int(timeRaw[1])
|
|
492
506
|
secondsRaw, microsecondsRaw, msw = decodeSecondsAndMicroSeconds(
|
|
493
|
-
timeRaw[2],
|
|
507
|
+
timeRaw[2],
|
|
508
|
+
data["Exif.Photo.SubSecTimeOriginal"] if isExifTagUsable(data, "Exif.Photo.SubSecTimeOriginal", float) else "0",
|
|
509
|
+
_,
|
|
494
510
|
)
|
|
495
511
|
warnings += msw
|
|
496
512
|
|
|
@@ -518,15 +534,19 @@ def decodeDateTimeOriginal(
|
|
|
518
534
|
# Otherwise, default to UTC + warning
|
|
519
535
|
else:
|
|
520
536
|
d = d.replace(tzinfo=datetime.timezone.utc)
|
|
521
|
-
warnings.append("Precise timezone information not found, fallback to UTC")
|
|
537
|
+
warnings.append(_("Precise timezone information not found, fallback to UTC"))
|
|
522
538
|
|
|
523
539
|
# Otherwise, default to UTC + warning
|
|
524
540
|
else:
|
|
525
541
|
d = d.replace(tzinfo=datetime.timezone.utc)
|
|
526
|
-
warnings.append("Precise timezone information not found (and no GPS coordinates to help), fallback to UTC")
|
|
542
|
+
warnings.append(_("Precise timezone information not found (and no GPS coordinates to help), fallback to UTC"))
|
|
527
543
|
|
|
528
544
|
except ValueError as e:
|
|
529
|
-
warnings.append(
|
|
545
|
+
warnings.append(
|
|
546
|
+
_("Skipping original date/time (from {datefield}) as it was not recognized: {v}").format(
|
|
547
|
+
datefield=datetimeField, v=data[datetimeField]
|
|
548
|
+
)
|
|
549
|
+
)
|
|
530
550
|
|
|
531
551
|
return (d, warnings)
|
|
532
552
|
|
|
@@ -538,7 +558,7 @@ def decodeTimeOffset(data: dict, offsetTimeField: str) -> Optional[datetime.tzin
|
|
|
538
558
|
|
|
539
559
|
|
|
540
560
|
def decodeGPSDateTime(
|
|
541
|
-
data: dict, group: str, lat: Optional[float] = None, lon: Optional[float] = None
|
|
561
|
+
data: dict, group: str, _: Callable[[str], str], lat: Optional[float] = None, lon: Optional[float] = None
|
|
542
562
|
) -> Tuple[Optional[datetime.datetime], List[str]]:
|
|
543
563
|
d = None
|
|
544
564
|
warnings = []
|
|
@@ -555,16 +575,20 @@ def decodeGPSDateTime(
|
|
|
555
575
|
elif isExifTagUsable(data, f"{group}.GPSDateTime", List[Fraction]):
|
|
556
576
|
timeRaw = decodeManyFractions(data[f"{group}.GPSDateTime"])
|
|
557
577
|
else:
|
|
558
|
-
|
|
578
|
+
timeRaw = None
|
|
579
|
+
warnings.append(
|
|
580
|
+
_("GPSTimeStamp and GPSDateTime don't contain supported time format (in {group} group)").format(group=group)
|
|
581
|
+
)
|
|
559
582
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
583
|
+
if timeRaw:
|
|
584
|
+
seconds, microseconds, msw = decodeSecondsAndMicroSeconds(
|
|
585
|
+
str(float(timeRaw[2])),
|
|
586
|
+
data["Exif.Photo.SubSecTimeOriginal"] if isExifTagUsable(data, "Exif.Photo.SubSecTimeOriginal", float) else "0",
|
|
587
|
+
_,
|
|
588
|
+
)
|
|
564
589
|
|
|
565
|
-
|
|
590
|
+
warnings += msw
|
|
566
591
|
|
|
567
|
-
if timeRaw:
|
|
568
592
|
d = datetime.datetime.combine(
|
|
569
593
|
datetime.date.fromisoformat(dateRaw),
|
|
570
594
|
datetime.time(
|
|
@@ -583,12 +607,16 @@ def decodeGPSDateTime(
|
|
|
583
607
|
d = d.astimezone(pytz.timezone(tz_name))
|
|
584
608
|
|
|
585
609
|
except ValueError as e:
|
|
586
|
-
warnings.append(
|
|
610
|
+
warnings.append(
|
|
611
|
+
_("Skipping GPS date/time ({group} group) as it was not recognized: {v}").format(
|
|
612
|
+
group=group, v=data[f"{group}.GPSDateStamp"]
|
|
613
|
+
)
|
|
614
|
+
)
|
|
587
615
|
|
|
588
616
|
return (d, warnings)
|
|
589
617
|
|
|
590
618
|
|
|
591
|
-
def decodeSecondsAndMicroSeconds(secondsRaw: str, microsecondsRaw: str) -> Tuple[int, int, List[str]]:
|
|
619
|
+
def decodeSecondsAndMicroSeconds(secondsRaw: str, microsecondsRaw: str, _: Callable[[str], str]) -> Tuple[int, int, List[str]]:
|
|
592
620
|
warnings = []
|
|
593
621
|
|
|
594
622
|
# Read microseconds from SubSecTime field
|
|
@@ -605,7 +633,9 @@ def decodeSecondsAndMicroSeconds(secondsRaw: str, microsecondsRaw: str) -> Tuple
|
|
|
605
633
|
# Check if microseconds from decimal seconds is not mismatching microseconds from SubSecTime field
|
|
606
634
|
if microseconds != microsecondsFromSeconds and microseconds > 0 and microsecondsFromSeconds > 0:
|
|
607
635
|
warnings.append(
|
|
608
|
-
|
|
636
|
+
_(
|
|
637
|
+
"Microseconds read from decimal seconds value ({microsecondsFromSeconds}) is not matching value from EXIF field ({microseconds}). Max value will be kept."
|
|
638
|
+
).format(microsecondsFromSeconds=microsecondsFromSeconds, microseconds=microseconds)
|
|
609
639
|
)
|
|
610
640
|
microseconds = max(microseconds, microsecondsFromSeconds)
|
|
611
641
|
else:
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# French translations for PACKAGE package.
|
|
2
|
+
# Copyright (C) 2024 THE PACKAGE'S COPYRIGHT HOLDER
|
|
3
|
+
# This file is distributed under the same license as the PACKAGE package.
|
|
4
|
+
# Adrien <panieravide@riseup.net>, 2024.
|
|
5
|
+
#
|
|
6
|
+
msgid ""
|
|
7
|
+
msgstr ""
|
|
8
|
+
"Project-Id-Version: PACKAGE VERSION\n"
|
|
9
|
+
"Report-Msgid-Bugs-To: \n"
|
|
10
|
+
"POT-Creation-Date: 2024-06-18 09:12+0200\n"
|
|
11
|
+
"PO-Revision-Date: 2024-06-18 09:09+0000\n"
|
|
12
|
+
"Last-Translator: PanierAvide <adrien@pavie.info>\n"
|
|
13
|
+
"Language-Team: French <http://weblate.panoramax.xyz/projects/panoramax/"
|
|
14
|
+
"tag-reader/fr/>\n"
|
|
15
|
+
"Language: fr\n"
|
|
16
|
+
"MIME-Version: 1.0\n"
|
|
17
|
+
"Content-Type: text/plain; charset=UTF-8\n"
|
|
18
|
+
"Content-Transfer-Encoding: 8bit\n"
|
|
19
|
+
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
|
20
|
+
"X-Generator: Weblate 5.4.3\n"
|
|
21
|
+
|
|
22
|
+
#: geopic_tag_reader/reader.py:177
|
|
23
|
+
msgid "Read latitude is out of WGS84 bounds (should be in [-90, 90])"
|
|
24
|
+
msgstr ""
|
|
25
|
+
"La latitude est hors des limites du WGS84 (devrait être entre [-90, 90])"
|
|
26
|
+
|
|
27
|
+
#: geopic_tag_reader/reader.py:179
|
|
28
|
+
msgid "Read longitude is out of WGS84 bounds (should be in [-180, 180])"
|
|
29
|
+
msgstr ""
|
|
30
|
+
"La longitude est hors des limites du WGS84 (devrait être entre [-180, 180])"
|
|
31
|
+
|
|
32
|
+
#: geopic_tag_reader/reader.py:221
|
|
33
|
+
msgid "Skipping Mapillary date/time as it was not recognized:"
|
|
34
|
+
msgstr "La date/heure de Mapillary est ignorée car non-reconnue :"
|
|
35
|
+
|
|
36
|
+
#: geopic_tag_reader/reader.py:234
|
|
37
|
+
msgid "Contradicting heading values found, GPSImgDirection value is used"
|
|
38
|
+
msgstr ""
|
|
39
|
+
"Valeurs d'orientation contradictoires, la valeur de GPSImgDirection est "
|
|
40
|
+
"utilisée"
|
|
41
|
+
|
|
42
|
+
#: geopic_tag_reader/reader.py:345
|
|
43
|
+
msgid "No GPS coordinates or broken coordinates in picture EXIF tags"
|
|
44
|
+
msgstr "Coordonnees GPS absentes ou invalides dans les attributs EXIF de l'image"
|
|
45
|
+
|
|
46
|
+
#: geopic_tag_reader/reader.py:351
|
|
47
|
+
msgid "No valid date in picture EXIF tags"
|
|
48
|
+
msgstr "Aucune date valide dans les attributs EXIF de l'image"
|
|
49
|
+
|
|
50
|
+
#: geopic_tag_reader/reader.py:356
|
|
51
|
+
msgid " and "
|
|
52
|
+
msgstr " et "
|
|
53
|
+
|
|
54
|
+
#: geopic_tag_reader/reader.py:423
|
|
55
|
+
msgid "Not a valid list of fractions"
|
|
56
|
+
msgstr "Liste de fractions invalide"
|
|
57
|
+
|
|
58
|
+
#: geopic_tag_reader/reader.py:436 geopic_tag_reader/reader.py:465
|
|
59
|
+
msgid "GPSLatitudeRef not found, assuming GPSLatitudeRef is North"
|
|
60
|
+
msgstr "GPSLatitudeRef non-trouvé, utilisation du Nord par défaut"
|
|
61
|
+
|
|
62
|
+
#: geopic_tag_reader/reader.py:444
|
|
63
|
+
msgid "Broken GPS coordinates in picture EXIF tags"
|
|
64
|
+
msgstr "Coordonnées GPS invalides dans les attributs EXIF de l'image"
|
|
65
|
+
|
|
66
|
+
#: geopic_tag_reader/reader.py:447 geopic_tag_reader/reader.py:471
|
|
67
|
+
msgid "GPSLongitudeRef not found, assuming GPSLongitudeRef is East"
|
|
68
|
+
msgstr "GPSLongitudeRef non-trouvé, utilisation de l'Est par défaut"
|
|
69
|
+
|
|
70
|
+
#: geopic_tag_reader/reader.py:522
|
|
71
|
+
msgid "Precise timezone information not found, fallback to UTC"
|
|
72
|
+
msgstr ""
|
|
73
|
+
"Aucune information précise de fuseau horaire trouvée, UTC utilisé par défaut"
|
|
74
|
+
|
|
75
|
+
#: geopic_tag_reader/reader.py:527
|
|
76
|
+
msgid ""
|
|
77
|
+
"Precise timezone information not found (and no GPS coordinates to help), "
|
|
78
|
+
"fallback to UTC"
|
|
79
|
+
msgstr ""
|
|
80
|
+
"Aucune information précise de fuseau horaire trouvée (ni de coordonnées GPS "
|
|
81
|
+
"pour aider), UTC utilisé par défaut"
|
|
82
|
+
|
|
83
|
+
#: geopic_tag_reader/reader.py:530
|
|
84
|
+
#, python-brace-format
|
|
85
|
+
msgid ""
|
|
86
|
+
"Skipping original date/time (from {datefield}) as it was not recognized:"
|
|
87
|
+
msgstr ""
|
|
88
|
+
"Date/heure originale (issue de {datefield}) ignorée car elle n'a pas été "
|
|
89
|
+
"reconnue :"
|
|
90
|
+
|
|
91
|
+
#: geopic_tag_reader/reader.py:559
|
|
92
|
+
#, python-brace-format
|
|
93
|
+
msgid ""
|
|
94
|
+
"GPSTimeStamp and GPSDateTime don't contain supported time format (in {group} "
|
|
95
|
+
"group)"
|
|
96
|
+
msgstr ""
|
|
97
|
+
"GPSTimeStamp et GPSDateTime ne contiennent pas un format d'heure valide ("
|
|
98
|
+
"dans le groupe {group})"
|
|
99
|
+
|
|
100
|
+
#: geopic_tag_reader/reader.py:587
|
|
101
|
+
#, python-brace-format
|
|
102
|
+
msgid "Skipping GPS date/time ({group} group) as it was not recognized:"
|
|
103
|
+
msgstr ""
|
|
104
|
+
"Date/heure du GPS (groupe {group}) ignorée car elle n'a pas été reconnue :"
|
|
105
|
+
|
|
106
|
+
#: geopic_tag_reader/reader.py:609
|
|
107
|
+
#, python-brace-format
|
|
108
|
+
msgid ""
|
|
109
|
+
"Microseconds read from decimal seconds value ({microsecondsFromSeconds}) is "
|
|
110
|
+
"not matching value from EXIF field ({microseconds}). Max value will be kept."
|
|
111
|
+
msgstr ""
|
|
112
|
+
"La valeur de micro-secondes lue à partir des secondes en valeurs décimales "
|
|
113
|
+
"({microsecondsFromSeconds}) ne correspond pas au champ EXIF dédié "
|
|
114
|
+
"({microseconds}). La valeur la plus élevée est conservée."
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# SOME DESCRIPTIVE TITLE.
|
|
2
|
+
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
3
|
+
# This file is distributed under the same license as the PACKAGE package.
|
|
4
|
+
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
|
5
|
+
#
|
|
6
|
+
#, fuzzy
|
|
7
|
+
msgid ""
|
|
8
|
+
msgstr ""
|
|
9
|
+
"Project-Id-Version: PACKAGE VERSION\n"
|
|
10
|
+
"Report-Msgid-Bugs-To: \n"
|
|
11
|
+
"POT-Creation-Date: 2024-07-10 08:17+0200\n"
|
|
12
|
+
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
|
13
|
+
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
14
|
+
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
15
|
+
"Language: \n"
|
|
16
|
+
"MIME-Version: 1.0\n"
|
|
17
|
+
"Content-Type: text/plain; charset=CHARSET\n"
|
|
18
|
+
"Content-Transfer-Encoding: 8bit\n"
|
|
19
|
+
|
|
20
|
+
#: geopic_tag_reader/main.py:26
|
|
21
|
+
msgid "Latitude:"
|
|
22
|
+
msgstr ""
|
|
23
|
+
|
|
24
|
+
#: geopic_tag_reader/main.py:27
|
|
25
|
+
msgid "Longitude:"
|
|
26
|
+
msgstr ""
|
|
27
|
+
|
|
28
|
+
#: geopic_tag_reader/main.py:28
|
|
29
|
+
msgid "Timestamp:"
|
|
30
|
+
msgstr ""
|
|
31
|
+
|
|
32
|
+
#: geopic_tag_reader/main.py:29
|
|
33
|
+
msgid "Heading:"
|
|
34
|
+
msgstr ""
|
|
35
|
+
|
|
36
|
+
#: geopic_tag_reader/main.py:30
|
|
37
|
+
msgid "Type:"
|
|
38
|
+
msgstr ""
|
|
39
|
+
|
|
40
|
+
#: geopic_tag_reader/main.py:31
|
|
41
|
+
msgid "Make:"
|
|
42
|
+
msgstr ""
|
|
43
|
+
|
|
44
|
+
#: geopic_tag_reader/main.py:32
|
|
45
|
+
msgid "Model:"
|
|
46
|
+
msgstr ""
|
|
47
|
+
|
|
48
|
+
#: geopic_tag_reader/main.py:33
|
|
49
|
+
msgid "Focal length:"
|
|
50
|
+
msgstr ""
|
|
51
|
+
|
|
52
|
+
#: geopic_tag_reader/main.py:34
|
|
53
|
+
msgid "Crop parameters:"
|
|
54
|
+
msgstr ""
|
|
55
|
+
|
|
56
|
+
#: geopic_tag_reader/main.py:35
|
|
57
|
+
msgid "Pitch:"
|
|
58
|
+
msgstr ""
|
|
59
|
+
|
|
60
|
+
#: geopic_tag_reader/main.py:36
|
|
61
|
+
msgid "Roll:"
|
|
62
|
+
msgstr ""
|
|
63
|
+
|
|
64
|
+
#: geopic_tag_reader/main.py:39
|
|
65
|
+
msgid "Warnings raised by reader:"
|
|
66
|
+
msgstr ""
|
|
67
|
+
|
|
68
|
+
#: geopic_tag_reader/reader.py:183
|
|
69
|
+
msgid "Read latitude is out of WGS84 bounds (should be in [-90, 90])"
|
|
70
|
+
msgstr ""
|
|
71
|
+
|
|
72
|
+
#: geopic_tag_reader/reader.py:185
|
|
73
|
+
msgid "Read longitude is out of WGS84 bounds (should be in [-180, 180])"
|
|
74
|
+
msgstr ""
|
|
75
|
+
|
|
76
|
+
#: geopic_tag_reader/reader.py:227
|
|
77
|
+
#, python-brace-format
|
|
78
|
+
msgid "Skipping Mapillary date/time as it was not recognized: {v}"
|
|
79
|
+
msgstr ""
|
|
80
|
+
|
|
81
|
+
#: geopic_tag_reader/reader.py:240
|
|
82
|
+
msgid "Contradicting heading values found, GPSImgDirection value is used"
|
|
83
|
+
msgstr ""
|
|
84
|
+
|
|
85
|
+
#: geopic_tag_reader/reader.py:351
|
|
86
|
+
msgid "No GPS coordinates or broken coordinates in picture EXIF tags"
|
|
87
|
+
msgstr ""
|
|
88
|
+
|
|
89
|
+
#: geopic_tag_reader/reader.py:357
|
|
90
|
+
msgid "No valid date in picture EXIF tags"
|
|
91
|
+
msgstr ""
|
|
92
|
+
|
|
93
|
+
#: geopic_tag_reader/reader.py:362
|
|
94
|
+
msgid "The picture is missing mandatory metadata:"
|
|
95
|
+
msgstr ""
|
|
96
|
+
|
|
97
|
+
#: geopic_tag_reader/reader.py:449 geopic_tag_reader/reader.py:478
|
|
98
|
+
msgid "GPSLatitudeRef not found, assuming GPSLatitudeRef is North"
|
|
99
|
+
msgstr ""
|
|
100
|
+
|
|
101
|
+
#: geopic_tag_reader/reader.py:457
|
|
102
|
+
msgid "Broken GPS coordinates in picture EXIF tags"
|
|
103
|
+
msgstr ""
|
|
104
|
+
|
|
105
|
+
#: geopic_tag_reader/reader.py:460 geopic_tag_reader/reader.py:484
|
|
106
|
+
msgid "GPSLongitudeRef not found, assuming GPSLongitudeRef is East"
|
|
107
|
+
msgstr ""
|
|
108
|
+
|
|
109
|
+
#: geopic_tag_reader/reader.py:537
|
|
110
|
+
msgid "Precise timezone information not found, fallback to UTC"
|
|
111
|
+
msgstr ""
|
|
112
|
+
|
|
113
|
+
#: geopic_tag_reader/reader.py:542
|
|
114
|
+
msgid ""
|
|
115
|
+
"Precise timezone information not found (and no GPS coordinates to help), "
|
|
116
|
+
"fallback to UTC"
|
|
117
|
+
msgstr ""
|
|
118
|
+
|
|
119
|
+
#: geopic_tag_reader/reader.py:546
|
|
120
|
+
#, python-brace-format
|
|
121
|
+
msgid ""
|
|
122
|
+
"Skipping original date/time (from {datefield}) as it was not recognized: {v}"
|
|
123
|
+
msgstr ""
|
|
124
|
+
|
|
125
|
+
#: geopic_tag_reader/reader.py:580
|
|
126
|
+
#, python-brace-format
|
|
127
|
+
msgid ""
|
|
128
|
+
"GPSTimeStamp and GPSDateTime don't contain supported time format (in {group} "
|
|
129
|
+
"group)"
|
|
130
|
+
msgstr ""
|
|
131
|
+
|
|
132
|
+
#: geopic_tag_reader/reader.py:611
|
|
133
|
+
#, python-brace-format
|
|
134
|
+
msgid "Skipping GPS date/time ({group} group) as it was not recognized: {v}"
|
|
135
|
+
msgstr ""
|
|
136
|
+
|
|
137
|
+
#: geopic_tag_reader/reader.py:637
|
|
138
|
+
#, python-brace-format
|
|
139
|
+
msgid ""
|
|
140
|
+
"Microseconds read from decimal seconds value ({microsecondsFromSeconds}) is "
|
|
141
|
+
"not matching value from EXIF field ({microseconds}). Max value will be kept."
|
|
142
|
+
msgstr ""
|
|
143
|
+
|
|
144
|
+
#: geopic_tag_reader/writer.py:132
|
|
145
|
+
#, python-brace-format
|
|
146
|
+
msgid "Unsupported key in additional tags ({k})"
|
|
147
|
+
msgstr ""
|
geopic_tag_reader/writer.py
CHANGED
|
@@ -5,6 +5,7 @@ from geopic_tag_reader.model import PictureType
|
|
|
5
5
|
from enum import Enum
|
|
6
6
|
import timezonefinder # type: ignore
|
|
7
7
|
import pytz
|
|
8
|
+
from geopic_tag_reader.i18n import init as i18n_init
|
|
8
9
|
|
|
9
10
|
try:
|
|
10
11
|
import pyexiv2 # type: ignore
|
|
@@ -72,10 +73,13 @@ def _fraction(value: float):
|
|
|
72
73
|
return f"{int(value * FLOAT_PRECISION)}/{FLOAT_PRECISION}"
|
|
73
74
|
|
|
74
75
|
|
|
75
|
-
def writePictureMetadata(picture: bytes, metadata: PictureMetadata) -> bytes:
|
|
76
|
+
def writePictureMetadata(picture: bytes, metadata: PictureMetadata, lang_code: str = "en") -> bytes:
|
|
76
77
|
"""
|
|
77
78
|
Override exif metadata on raw picture and return updated bytes
|
|
78
79
|
"""
|
|
80
|
+
|
|
81
|
+
_ = i18n_init(lang_code)
|
|
82
|
+
|
|
79
83
|
if not metadata.has_change():
|
|
80
84
|
return picture
|
|
81
85
|
|
|
@@ -125,7 +129,7 @@ def writePictureMetadata(picture: bytes, metadata: PictureMetadata) -> bytes:
|
|
|
125
129
|
elif k.startswith("Exif."):
|
|
126
130
|
updated_exif.update({k: v})
|
|
127
131
|
else:
|
|
128
|
-
raise UnsupportedExifTagException(
|
|
132
|
+
raise UnsupportedExifTagException(_("Unsupported key in additional tags ({k})").format(k=k))
|
|
129
133
|
|
|
130
134
|
if updated_exif:
|
|
131
135
|
img.modify_exif(updated_exif)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: geopic-tag-reader
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.3
|
|
4
4
|
Summary: GeoPicTagReader
|
|
5
5
|
Author-email: Adrien PAVIE <panieravide@riseup.net>
|
|
6
6
|
Requires-Python: >=3.8
|
|
@@ -17,39 +17,26 @@ Requires-Dist: black ~= 24.3 ; extra == "dev"
|
|
|
17
17
|
Requires-Dist: mypy ~= 1.9 ; extra == "dev"
|
|
18
18
|
Requires-Dist: pytest ~= 7.2.0 ; extra == "dev"
|
|
19
19
|
Requires-Dist: pytest-datafiles ~= 3.0 ; extra == "dev"
|
|
20
|
-
Requires-Dist: lazydocs ~= 0.4.8 ; extra == "dev"
|
|
21
20
|
Requires-Dist: types-xmltodict ~= 0.13 ; extra == "dev"
|
|
22
21
|
Requires-Dist: pre-commit ~= 3.3.3 ; extra == "dev"
|
|
22
|
+
Requires-Dist: mkdocs-material ~= 9.5.21 ; extra == "docs"
|
|
23
|
+
Requires-Dist: mkdocstrings[python] ~= 0.25.1 ; extra == "docs"
|
|
23
24
|
Requires-Dist: python-dateutil ~= 2.8.2 ; extra == "write-exif"
|
|
24
|
-
|
|
25
|
+
Requires-Dist: types-python-dateutil ~= 2.9.0.20240316 ; extra == "write-exif"
|
|
26
|
+
Project-URL: Home, https://gitlab.com/panoramax/server/geo-picture-tag-reader
|
|
25
27
|
Provides-Extra: build
|
|
26
28
|
Provides-Extra: dev
|
|
29
|
+
Provides-Extra: docs
|
|
27
30
|
Provides-Extra: write-exif
|
|
28
31
|
|
|
29
|
-
#  Panoramax
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
➡️ __Give it a try__ at [panoramax.ign.fr](https://panoramax.ign.fr/) or [geovisio.fr](https://geovisio.fr/viewer) !
|
|
34
|
-
|
|
35
|
-
## 📦 Components
|
|
36
|
-
|
|
37
|
-
GeoVisio is __modular__ and made of several components, each of them standardized and ♻️ replaceable.
|
|
38
|
-
|
|
39
|
-

|
|
40
|
-
|
|
41
|
-
All of them are 📖 __open-source__ and available online:
|
|
42
|
-
|
|
43
|
-
| 🌐 Server | 💻 Client |
|
|
44
|
-
|:-----------------------------------------------------------------------:|:----------------------------------------------------:|
|
|
45
|
-
| [API](https://gitlab.com/geovisio/api) | [Website](https://gitlab.com/geovisio/website) |
|
|
46
|
-
| [Blur API](https://gitlab.com/geovisio/blurring) | [Web viewer](https://gitlab.com/geovisio/web-viewer) |
|
|
47
|
-
| [GeoPic Tag Reader](https://gitlab.com/geovisio/geo-picture-tag-reader) | [Command line](https://gitlab.com/geovisio/cli) |
|
|
34
|
+
__Panoramax__ is a digital resource for sharing and exploiting 📍📷 field photos. Anyone can take photographs of places visible from the public streets and contribute them to the Panoramax database. This data is then freely accessible and reusable by all. More information available at [gitlab.com/panoramax](https://gitlab.com/panoramax) and [panoramax.fr](https://panoramax.fr/).
|
|
48
35
|
|
|
49
36
|
|
|
50
37
|
# 📷 GeoPic Tag Reader
|
|
51
38
|
|
|
52
|
-
This repository only contains the Python library to __read and write standardized metadata__ from geolocated pictures EXIF metadata.
|
|
39
|
+
This repository only contains the Python library to __read and write standardized metadata__ from geolocated pictures EXIF metadata. It can be used completely apart from all GeoVisio/Panoramax components for your own projects and needs.
|
|
53
40
|
|
|
54
41
|
## Features
|
|
55
42
|
|
|
@@ -71,7 +58,7 @@ geopic-tag-reader --help
|
|
|
71
58
|
|
|
72
59
|
To know more about install and other options, see [install documentation](./docs/Install.md).
|
|
73
60
|
|
|
74
|
-
If at some point you're lost or need help, you can contact us through [issues](https://gitlab.com/
|
|
61
|
+
If at some point you're lost or need help, you can contact us through [issues](https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/issues) or by [email](mailto:panieravide@riseup.net).
|
|
75
62
|
|
|
76
63
|
|
|
77
64
|
## Usage
|
|
@@ -137,26 +124,12 @@ editedImg.close()
|
|
|
137
124
|
|
|
138
125
|
## Contributing
|
|
139
126
|
|
|
140
|
-
Pull requests are welcome. For major changes, please open an [issue](https://gitlab.com/
|
|
127
|
+
Pull requests are welcome. For major changes, please open an [issue](https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/issues) first to discuss what you would like to change.
|
|
141
128
|
|
|
142
129
|
More information about developing is available in [documentation](./docs/Develop.md).
|
|
143
130
|
|
|
144
131
|
|
|
145
|
-
## 🤗 Special thanks
|
|
146
|
-
|
|
147
|
-

|
|
148
|
-
|
|
149
|
-
GeoVisio was made possible thanks to a group of ✨ __amazing__ people ✨ :
|
|
150
|
-
|
|
151
|
-
- __[GéoVélo](https://geovelo.fr/)__ team, for 💶 funding initial development and for 🔍 testing/improving software
|
|
152
|
-
- __[Carto Cité](https://cartocite.fr/)__ team (in particular Antoine Riche), for 💶 funding improvements on viewer (map browser, flat pictures support)
|
|
153
|
-
- __[La Fabrique des Géocommuns (IGN)](https://www.ign.fr/institut/la-fabrique-des-geocommuns-incubateur-de-communs-lign)__ for offering long-term support and funding the [Panoramax](https://panoramax.fr/) initiative and core team (Camille Salou, Mathilde Ferrey, Christian Quest, Antoine Desbordes, Jean Andreani, Adrien Pavie)
|
|
154
|
-
- Many _many_ __wonderful people__ who worked on various parts of GeoVisio or core dependencies we use : 🧙 Stéphane Péneau, 🎚 Albin Calais & Cyrille Giquello, 📷 [Damien Sorel](https://www.strangeplanet.fr/), Pascal Rhod, Nick Whitelegg...
|
|
155
|
-
- __[Adrien Pavie](https://pavie.info/)__, for ⚙️ initial development of GeoVisio
|
|
156
|
-
- And you all ✨ __GeoVisio users__ for making this project useful !
|
|
157
|
-
|
|
158
|
-
|
|
159
132
|
## ⚖️ License
|
|
160
133
|
|
|
161
|
-
Copyright (c)
|
|
134
|
+
Copyright (c) Panoramax team 2022-2024, [released under MIT license](https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/blob/main/LICENSE).
|
|
162
135
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
geopic_tag_reader/__init__.py,sha256=5U9FfeZGkInbD3H9zaqteUjP5wK3mmNATj3vZPy31pE,47
|
|
2
|
+
geopic_tag_reader/camera.py,sha256=Nw6dQjnrUCCOXujjk8Y7IwjJPMuDf4DAGCmHk0LDfEg,1975
|
|
3
|
+
geopic_tag_reader/i18n.py,sha256=LOLBj7eB_hpHTc5XdMP97EoWdD2kgmkP_uvJJDKEVsU,342
|
|
4
|
+
geopic_tag_reader/main.py,sha256=NJFDdPlyj0HvBcPABTVPqdSPm0SP3lnW4HF1fmhONY4,3550
|
|
5
|
+
geopic_tag_reader/model.py,sha256=rsWVE3T1kpNsKXX8iv6xb_3PCVY6Ea7iU9WOqUgXklU,129
|
|
6
|
+
geopic_tag_reader/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
geopic_tag_reader/reader.py,sha256=4tk-qvN-mBryp8FbtG9smQkSlWDjWZU1CDedm07MqP0,25746
|
|
8
|
+
geopic_tag_reader/writer.py,sha256=HdZenoY_5Qv1Kq0jedCJhVFDYsv0iQaCzB6necU_LrY,8793
|
|
9
|
+
geopic_tag_reader/translations/geopic_tag_reader.pot,sha256=1myoxYDZfHh7R_Iiyn9VFdz5T6nbYR3S_jxFhkrw0GI,3590
|
|
10
|
+
geopic_tag_reader/translations/fr/LC_MESSAGES/geopic_tag_reader.po,sha256=QxuV0AYxOAVCI4xaKDDCvgUfeym6oRQC2YeRJpI9i9c,4223
|
|
11
|
+
geopic_tag_reader-1.1.3.dist-info/entry_points.txt,sha256=c9YwjCNhxveDf-61_aSRlzcpoutvM6KQCerlzaVt_JU,64
|
|
12
|
+
geopic_tag_reader-1.1.3.dist-info/LICENSE,sha256=oHWDwXkJJb9zJzThMN3F9Li4yFhz1qxOUByouY7L3bI,1070
|
|
13
|
+
geopic_tag_reader-1.1.3.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
|
|
14
|
+
geopic_tag_reader-1.1.3.dist-info/METADATA,sha256=UWeERQ-wFwg-URJUXsRaGJLzcuJq8BkQNmw-3jSpcJ0,4578
|
|
15
|
+
geopic_tag_reader-1.1.3.dist-info/RECORD,,
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
geopic_tag_reader/__init__.py,sha256=7hT1At7Ce4FtNeMuBZa7cDah_cJoIhJrnQWbEAF0BE8,47
|
|
2
|
-
geopic_tag_reader/camera.py,sha256=2Sr0jAt3RXUWazYMnkwF6J6lVnKvSp7Ac8g7yOHehVA,1643
|
|
3
|
-
geopic_tag_reader/main.py,sha256=ZEZaZEeaDxRjrVMwhR5lUYJWKkUcjd8avjqm7JxJdhM,3219
|
|
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=4yoQU-ljOgLmaH_hc1m4o1YkSNgaPfM38NVmiM7rpbE,24543
|
|
7
|
-
geopic_tag_reader/writer.py,sha256=QmQqQpWgb6AL3Y0Kuzy7PF1asUsTVDq9buwJXfFSRq8,8672
|
|
8
|
-
geopic_tag_reader-1.1.1.dist-info/entry_points.txt,sha256=c9YwjCNhxveDf-61_aSRlzcpoutvM6KQCerlzaVt_JU,64
|
|
9
|
-
geopic_tag_reader-1.1.1.dist-info/LICENSE,sha256=oHWDwXkJJb9zJzThMN3F9Li4yFhz1qxOUByouY7L3bI,1070
|
|
10
|
-
geopic_tag_reader-1.1.1.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
|
|
11
|
-
geopic_tag_reader-1.1.1.dist-info/METADATA,sha256=iMhClAI5r1h2jpB_o-dwcSP3QVz4PuBPf4jWmZ_10Ks,6303
|
|
12
|
-
geopic_tag_reader-1.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|