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.
@@ -2,4 +2,4 @@
2
2
  GeoPicTagReader
3
3
  """
4
4
 
5
- __version__ = "1.1.1"
5
+ __version__ = "1.1.3"
@@ -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
- return any(model.startswith(potentialModel) for potentialModel in EQUIRECTANGULAR_MODELS.get(make, [])) and (
48
- (width is None or height is None) or int(width) == 2 * int(height)
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
- print("Latitude:", metadata.lat)
24
- print("Longitude:", metadata.lon)
25
- print("Timestamp:", metadata.ts.isoformat())
26
- print("Heading:", metadata.heading)
27
- print("Type:", metadata.type)
28
- print("Make:", metadata.make)
29
- print("Model:", metadata.model)
30
- print("Focal length:", metadata.focal_length)
31
- print("Crop parameters:", metadata.crop)
32
- print("Pitch:", metadata.pitch)
33
- print("Roll:", metadata.roll)
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
@@ -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:\n\t" + str(e))
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
- " and ".join(errors),
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 ValueError()
432
+ raise InvalidFractionException()
419
433
  return vals
420
434
 
421
435
  except:
422
- raise ValueError("Not a valid list of fractions")
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], data["Exif.Photo.SubSecTimeOriginal"] if isExifTagUsable(data, "Exif.Photo.SubSecTimeOriginal", float) else "0"
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("Skipping original date/time (from " + datetimeField + ") as it was not recognized:\n\t" + str(e))
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
- raise ValueError(f"GPSTimeStamp and GPSDateTime don't contain supported time format (in {group} group)")
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
- seconds, microseconds, msw = decodeSecondsAndMicroSeconds(
561
- str(float(timeRaw[2])),
562
- data["Exif.Photo.SubSecTimeOriginal"] if isExifTagUsable(data, "Exif.Photo.SubSecTimeOriginal", float) else "0",
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
- warnings += msw
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(f"Skipping GPS date/time ({group} group) as it was not recognized:\n\t{str(e)}")
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
- f"Microseconds read from decimal seconds value ({microsecondsFromSeconds}) is not matching value from EXIF field ({microseconds}). Max value will be kept."
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 ""
@@ -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(f"Unsupported key in additional tags ({k})")
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.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
- Project-URL: Home, https://gitlab.com/geovisio/geo-picture-tag-reader
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
- # ![GeoVisio](https://gitlab.com/geovisio/api/-/raw/develop/images/logo_full.png)
32
+ # ![Panoramax](https://upload.wikimedia.org/wikipedia/commons/thumb/a/a9/Panoramax.svg/40px-Panoramax.svg.png) Panoramax
30
33
 
31
- __GeoVisio__ is a complete solution for storing and __serving your own 📍📷 geolocated pictures__ (like [StreetView](https://www.google.com/streetview/) / [Mapillary](https://mapillary.com/)).
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
- ![GeoVisio architecture](https://gitlab.com/geovisio/api/-/raw/develop/images/big_picture.png)
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/geovisio/geo-picture-tag-reader/-/issues) or by [email](mailto:panieravide@riseup.net).
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/geovisio/geo-picture-tag-reader/-/issues) first to discuss what you would like to change.
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
- ![Sponsors](https://gitlab.com/geovisio/api/-/raw/develop/images/sponsors.png)
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) GeoVisio team 2022-2023, [released under MIT license](https://gitlab.com/geovisio/geo-picture-tag-reader/-/blob/main/LICENSE).
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,,