geopic-tag-reader 1.1.4__tar.gz → 1.2.0__tar.gz

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.
Files changed (35) hide show
  1. {geopic_tag_reader-1.1.4 → geopic_tag_reader-1.2.0}/.gitlab-ci.yml +15 -0
  2. {geopic_tag_reader-1.1.4 → geopic_tag_reader-1.2.0}/CHANGELOG.md +22 -1
  3. {geopic_tag_reader-1.1.4 → geopic_tag_reader-1.2.0}/PKG-INFO +1 -1
  4. {geopic_tag_reader-1.1.4 → geopic_tag_reader-1.2.0}/docs/index.md +6 -6
  5. {geopic_tag_reader-1.1.4 → geopic_tag_reader-1.2.0}/docs/tech/api_reference.md +4 -0
  6. {geopic_tag_reader-1.1.4 → geopic_tag_reader-1.2.0}/geopic_tag_reader/__init__.py +1 -1
  7. {geopic_tag_reader-1.1.4 → geopic_tag_reader-1.2.0}/geopic_tag_reader/main.py +1 -0
  8. {geopic_tag_reader-1.1.4 → geopic_tag_reader-1.2.0}/geopic_tag_reader/reader.py +28 -40
  9. geopic_tag_reader-1.2.0/geopic_tag_reader/sequence.py +301 -0
  10. geopic_tag_reader-1.2.0/geopic_tag_reader/translations/de/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  11. geopic_tag_reader-1.2.0/geopic_tag_reader/translations/de/LC_MESSAGES/geopic_tag_reader.po +165 -0
  12. geopic_tag_reader-1.2.0/geopic_tag_reader/translations/en/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  13. {geopic_tag_reader-1.1.4 → geopic_tag_reader-1.2.0}/geopic_tag_reader/translations/en/LC_MESSAGES/geopic_tag_reader.po +22 -22
  14. geopic_tag_reader-1.2.0/geopic_tag_reader/translations/es/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  15. geopic_tag_reader-1.1.4/geopic_tag_reader/translations/geopic_tag_reader.pot → geopic_tag_reader-1.2.0/geopic_tag_reader/translations/es/LC_MESSAGES/geopic_tag_reader.po +5 -6
  16. geopic_tag_reader-1.2.0/geopic_tag_reader/translations/fr/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  17. geopic_tag_reader-1.2.0/geopic_tag_reader/translations/geopic_tag_reader.pot +147 -0
  18. {geopic_tag_reader-1.1.4 → geopic_tag_reader-1.2.0}/pyproject.toml +1 -0
  19. {geopic_tag_reader-1.1.4 → geopic_tag_reader-1.2.0}/.gitignore +0 -0
  20. {geopic_tag_reader-1.1.4 → geopic_tag_reader-1.2.0}/.pre-commit-config.yaml +0 -0
  21. {geopic_tag_reader-1.1.4 → geopic_tag_reader-1.2.0}/CODE_OF_CONDUCT.md +0 -0
  22. {geopic_tag_reader-1.1.4 → geopic_tag_reader-1.2.0}/LICENSE +0 -0
  23. {geopic_tag_reader-1.1.4 → geopic_tag_reader-1.2.0}/Makefile +0 -0
  24. {geopic_tag_reader-1.1.4 → geopic_tag_reader-1.2.0}/README.md +0 -0
  25. {geopic_tag_reader-1.1.4 → geopic_tag_reader-1.2.0}/docs/develop.md +0 -0
  26. {geopic_tag_reader-1.1.4 → geopic_tag_reader-1.2.0}/docs/install.md +0 -0
  27. {geopic_tag_reader-1.1.4 → geopic_tag_reader-1.2.0}/docs/tech/cli.md +0 -0
  28. {geopic_tag_reader-1.1.4 → geopic_tag_reader-1.2.0}/geopic_tag_reader/camera.py +0 -0
  29. {geopic_tag_reader-1.1.4 → geopic_tag_reader-1.2.0}/geopic_tag_reader/i18n.py +0 -0
  30. {geopic_tag_reader-1.1.4 → geopic_tag_reader-1.2.0}/geopic_tag_reader/model.py +0 -0
  31. {geopic_tag_reader-1.1.4 → geopic_tag_reader-1.2.0}/geopic_tag_reader/py.typed +0 -0
  32. {geopic_tag_reader-1.1.4 → geopic_tag_reader-1.2.0}/geopic_tag_reader/translations/fr/LC_MESSAGES/geopic_tag_reader.po +0 -0
  33. {geopic_tag_reader-1.1.4 → geopic_tag_reader-1.2.0}/geopic_tag_reader/writer.py +0 -0
  34. {geopic_tag_reader-1.1.4 → geopic_tag_reader-1.2.0}/mkdocs.yml +0 -0
  35. {geopic_tag_reader-1.1.4 → geopic_tag_reader-1.2.0}/pytest.ini +0 -0
@@ -38,6 +38,21 @@ tests-writer:
38
38
  - make i18n-po2code
39
39
  - pytest -s -vv
40
40
 
41
+ # tests-deploy_pypi:
42
+ # stage: deploy
43
+ # image: python:3.8
44
+ # variables:
45
+ # FLIT_INDEX_URL: https://test.pypi.org/legacy/
46
+ # FLIT_USERNAME: $TEST_FLIT_USERNAME
47
+ # FLIT_PASSWORD: $TEST_FLIT_PASSWORD
48
+ # script:
49
+ # - apt update && apt install -y gcc git gettext
50
+ # - pip install .[build]
51
+ # - make i18n-po2code
52
+ # - flit publish
53
+ # only:
54
+ # - develop
55
+
41
56
  deploy_pypi:
42
57
  stage: deploy
43
58
  image: python:3.8
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.2.0] - 2024-07-30
11
+
12
+ ### Added
13
+
14
+ - A new module `sequence` is available through Python to dispatch a set of pictures (based on their metadata) into several sequences, based on de-duplicate and split parameters. This is based on existing code previously stored in [command-line client](https://gitlab.com/panoramax/clients/cli), moved here to be shared between API and CLI.
15
+
16
+ ### Changed
17
+
18
+ - Reader offers a `yaw` value (360° sphere correction), distinct from `heading` (GPS direction). EXIF tags are read a bit differently to reflect this : `yaw` comes from `Xmp.Camera.Yaw & Xmp.GPano.PoseHeadingDegrees`, `heading` from `Exif.GPSInfo.GPSImgDirection & MAPCompassHeading`.
19
+
20
+ ### Removed
21
+ - Previously used values `Exif.GPSInfo.GPS(Pitch|Roll)` and `Xmp.GPano.InitialView(Pitch|Roll)Degrees` are dropped, first one for not being standard, second one for not being correct to use for pitch/roll (prefer `Xmp.GPano.Pose(Pitch|Roll)Degrees`).
22
+
23
+ ## [1.1.5] - 2024-07-10
24
+
25
+ ### Fixed
26
+
27
+ - PyPI package was missing built translation files (MO files).
28
+
10
29
  ## [1.1.4] - 2024-07-10
11
30
 
12
31
  ### Changed
@@ -193,7 +212,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
193
212
 
194
213
  - EXIF tag reading methods extracted from [GeoVisio API](https://gitlab.com/panoramax/server/api)
195
214
 
196
- [Unreleased]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.1.4...main
215
+ [Unreleased]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.2.0...main
216
+ [1.2.0]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.1.5...1.2.0
217
+ [1.1.5]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.1.4...1.1.5
197
218
  [1.1.4]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.1.3...1.1.4
198
219
  [1.1.3]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.1.2...1.1.3
199
220
  [1.1.2]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.1.1...1.1.2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: geopic-tag-reader
3
- Version: 1.1.4
3
+ Version: 1.2.0
4
4
  Summary: GeoPicTagReader
5
5
  Author-email: Adrien PAVIE <panieravide@riseup.net>
6
6
  Requires-Python: >=3.8
@@ -50,7 +50,6 @@ The following EXIF tags are recognized and used if defined, but are **optional**
50
50
  Image orientation is read from
51
51
 
52
52
  - `GPSImgDirection`
53
- - `GPano:PoseHeadingDegrees`
54
53
  - or in [Mapillary](https://www.mapillary.com/) tags: `MAPCompassHeading`
55
54
 
56
55
  #### :material-timer: Milliseconds in date
@@ -80,21 +79,22 @@ Camera focal length (to get precise field of view) is read from:
80
79
  - `Exif.Image.FocalLength`
81
80
  - `Exif.Photo.FocalLength`
82
81
 
83
- #### :octicons-horizontal-rule-16: Pitch and roll
82
+ #### :octicons-horizontal-rule-16: Yaw, Pitch and Roll
83
+
84
+ Yaw value is read from:
85
+
86
+ - `Xmp.Camera.Yaw`
87
+ - `Xmp.GPano.PoseHeadingDegrees`
84
88
 
85
89
  Pitch value is read from:
86
90
 
87
91
  - `Xmp.Camera.Pitch`
88
- - `Exif.GPSInfo.GPSPitch`
89
92
  - `Xmp.GPano.PosePitchDegrees`
90
- - `Xmp.GPano.InitialViewPitchDegrees`
91
93
 
92
94
  Roll value is read from:
93
95
 
94
96
  - `Xmp.Camera.Roll`
95
- - `Exif.GPSInfo.GPSRoll`
96
97
  - `Xmp.GPano.PoseRollDegrees`
97
- - `Xmp.GPano.InitialViewRollDegrees`
98
98
 
99
99
  #### ⛰️ Altitude
100
100
 
@@ -16,6 +16,10 @@
16
16
 
17
17
  ::: geopic_tag_reader.writer
18
18
 
19
+ ## Sequence
20
+
21
+ ::: geopic_tag_reader.sequence
22
+
19
23
  ## Camera
20
24
 
21
25
  ::: geopic_tag_reader.camera
@@ -2,4 +2,4 @@
2
2
  GeoPicTagReader
3
3
  """
4
4
 
5
- __version__ = "1.1.4"
5
+ __version__ = "1.2.0"
@@ -34,6 +34,7 @@ def read(
34
34
  print(_("Crop parameters:"), metadata.crop)
35
35
  print(_("Pitch:"), metadata.pitch)
36
36
  print(_("Roll:"), metadata.roll)
37
+ print(_("Yaw:"), metadata.yaw)
37
38
 
38
39
  if len(metadata.tagreader_warnings) > 0:
39
40
  print(_("Warnings raised by reader:"))
@@ -47,7 +47,7 @@ class GeoPicTags:
47
47
  lat (float): GPS Latitude (in WGS84)
48
48
  lon (float): GPS Longitude (in WGS84)
49
49
  ts (datetime): The capture date (date & time with timezone)
50
- heading (int): Picture heading/yaw (in degrees, North = 0°, East = 90°, South = 180°, West = 270°)
50
+ heading (int): Picture GPS heading (in degrees, North = 0°, East = 90°, South = 180°, West = 270°). Value is computed based on image center (if yaw=0°)
51
51
  type (str): The kind of picture (flat, equirectangular)
52
52
  make (str): The camera manufacturer name
53
53
  model (str): The camera model name
@@ -58,6 +58,7 @@ class GeoPicTags:
58
58
  altitude (float): altitude (in m) (optional)
59
59
  pitch (float): Picture pitch angle, compared to horizon (in degrees, bottom = -90°, horizon = 0°, top = 90°)
60
60
  roll (float): Picture roll angle, on a right/left axis (in degrees, left-arm down = -90°, flat = 0°, right-arm down = 90°)
61
+ yaw (float): Picture yaw angle, on a vertical axis (in degrees, front = 0°, right = 90°, rear = 180°, left = 270°). This offsets the center image from GPS direction for a correct 360° sphere correction
61
62
 
62
63
 
63
64
  Implementation note: this needs to be sync with the PartialGeoPicTags structure
@@ -77,6 +78,7 @@ class GeoPicTags:
77
78
  altitude: Optional[float] = None
78
79
  pitch: Optional[float] = None
79
80
  roll: Optional[float] = None
81
+ yaw: Optional[float] = None
80
82
 
81
83
 
82
84
  class InvalidExifException(Exception):
@@ -111,6 +113,7 @@ class PartialGeoPicTags:
111
113
  altitude: Optional[float] = None
112
114
  pitch: Optional[float] = None
113
115
  roll: Optional[float] = None
116
+ yaw: Optional[float] = None
114
117
 
115
118
 
116
119
  class PartialExifException(Exception):
@@ -226,57 +229,40 @@ def readPictureMetadata(picture: bytes, lang_code: str = "en") -> GeoPicTags:
226
229
  except Exception as e:
227
230
  warnings.append(_("Skipping Mapillary date/time as it was not recognized: {v}").format(v=data["MAPGpsTime"]))
228
231
 
229
- # Heading/Yaw
232
+ # GPS Heading
230
233
  heading = None
231
- if isExifTagUsable(data, "Xmp.GPano.PoseHeadingDegrees", float) and isExifTagUsable(data, "Exif.GPSInfo.GPSImgDirection", Fraction):
232
- gpsDir = int(round(float(Fraction(data["Exif.GPSInfo.GPSImgDirection"]))))
233
- gpanoHeading = int(round(float(data["Xmp.GPano.PoseHeadingDegrees"])))
234
- if gpsDir > 0 and gpanoHeading == 0:
235
- heading = gpsDir
236
- elif gpsDir == 0 and gpanoHeading > 0:
237
- heading = gpanoHeading
238
- else:
239
- if gpsDir != gpanoHeading:
240
- warnings.append(_("Contradicting heading values found, GPSImgDirection value is used"))
241
- heading = gpsDir
242
-
243
- elif isExifTagUsable(data, "Xmp.GPano.PoseHeadingDegrees", float):
244
- heading = int(round(float(data["Xmp.GPano.PoseHeadingDegrees"])))
245
-
246
- elif isExifTagUsable(data, "Exif.GPSInfo.GPSImgDirection", Fraction):
234
+ if isExifTagUsable(data, "Exif.GPSInfo.GPSImgDirection", Fraction):
247
235
  heading = int(round(float(Fraction(data["Exif.GPSInfo.GPSImgDirection"]))))
248
236
 
249
237
  elif "MAPCompassHeading" in data and isExifTagUsable(data["MAPCompassHeading"], "TrueHeading", float):
250
238
  heading = int(round(float(data["MAPCompassHeading"]["TrueHeading"])))
251
239
 
252
- # Pitch & roll
240
+ # Yaw / Pitch / roll
241
+ yaw = None
253
242
  pitch = None
254
243
  roll = None
255
- exifPRFields = ["Xmp.Camera.$$", "Exif.GPSInfo.GPS$$", "Xmp.GPano.Pose$$Degrees", "Xmp.GPano.InitialView$$Degrees"]
256
- # For each potential EXIF field
257
- for exifField in exifPRFields:
258
- # Try out both Pitch & Roll variants
259
- for checkField in ["Pitch", "Roll"]:
260
- exifCheckField = exifField.replace("$$", checkField)
244
+ exifYPRFields = {
245
+ "yaw": ["Xmp.Camera.Yaw", "Xmp.GPano.PoseHeadingDegrees"],
246
+ "pitch": ["Xmp.Camera.Pitch", "Xmp.GPano.PosePitchDegrees"],
247
+ "roll": ["Xmp.Camera.Roll", "Xmp.GPano.PoseRollDegrees"],
248
+ }
249
+ for ypr in exifYPRFields:
250
+ for exifTag in exifYPRFields[ypr]:
261
251
  foundValue = None
262
252
  # Look for float or fraction
263
- if isExifTagUsable(data, exifCheckField, float):
264
- foundValue = float(data[exifCheckField])
265
- elif isExifTagUsable(data, exifCheckField, Fraction):
266
- foundValue = float(Fraction(data[exifCheckField]))
253
+ if isExifTagUsable(data, exifTag, float):
254
+ foundValue = float(data[exifTag])
255
+ elif isExifTagUsable(data, exifTag, Fraction):
256
+ foundValue = float(Fraction(data[exifTag]))
267
257
 
268
- # Save to correct variable (if not already set)
258
+ # Save found value
269
259
  if foundValue is not None:
270
- if checkField == "Pitch":
271
- if pitch is None:
272
- pitch = foundValue
273
- else:
274
- continue
275
- elif checkField == "Roll":
276
- if roll is None:
277
- roll = foundValue
278
- else:
279
- continue
260
+ if ypr == "yaw" and yaw is None:
261
+ yaw = foundValue
262
+ elif ypr == "pitch" and pitch is None:
263
+ pitch = foundValue
264
+ elif ypr == "roll" and roll is None:
265
+ roll = foundValue
280
266
 
281
267
  # Make and model
282
268
  make = data.get("Exif.Image.Make") or data.get("MAPDeviceMake")
@@ -383,6 +369,7 @@ def readPictureMetadata(picture: bytes, lang_code: str = "en") -> GeoPicTags:
383
369
  altitude=altitude,
384
370
  pitch=pitch,
385
371
  roll=roll,
372
+ yaw=yaw,
386
373
  ),
387
374
  )
388
375
 
@@ -402,6 +389,7 @@ def readPictureMetadata(picture: bytes, lang_code: str = "en") -> GeoPicTags:
402
389
  altitude=altitude,
403
390
  pitch=pitch,
404
391
  roll=roll,
392
+ yaw=yaw,
405
393
  )
406
394
 
407
395
 
@@ -0,0 +1,301 @@
1
+ from dataclasses import dataclass
2
+ from enum import Enum
3
+ from typing import Optional, List, Tuple
4
+ from pathlib import PurePath
5
+ from geopic_tag_reader.reader import GeoPicTags
6
+ import datetime
7
+ import math
8
+
9
+
10
+ class SortMethod(str, Enum):
11
+ filename_asc = "filename-asc"
12
+ filename_desc = "filename-desc"
13
+ time_asc = "time-asc"
14
+ time_desc = "time-desc"
15
+
16
+
17
+ @dataclass
18
+ class MergeParams:
19
+ maxDistance: Optional[float] = None
20
+ maxRotationAngle: Optional[int] = None
21
+
22
+ def is_merge_needed(self):
23
+ # Only check max distance, as max rotation angle is only useful when dist is defined
24
+ return self.maxDistance is not None
25
+
26
+
27
+ @dataclass
28
+ class SplitParams:
29
+ maxDistance: Optional[int] = None
30
+ maxTime: Optional[int] = None
31
+
32
+ def is_split_needed(self):
33
+ return self.maxDistance is not None or self.maxTime is not None
34
+
35
+
36
+ @dataclass
37
+ class Picture:
38
+ filename: str
39
+ metadata: GeoPicTags
40
+
41
+ def distance_to(self, other) -> float:
42
+ """Computes distance in meters based on Haversine formula"""
43
+ R = 6371000
44
+ phi1 = math.radians(self.metadata.lat)
45
+ phi2 = math.radians(other.metadata.lat)
46
+ delta_phi = math.radians(other.metadata.lat - self.metadata.lat)
47
+ delta_lambda = math.radians(other.metadata.lon - self.metadata.lon)
48
+ a = math.sin(delta_phi / 2.0) ** 2 + math.cos(phi1) * math.cos(phi2) * math.sin(delta_lambda / 2.0) ** 2
49
+ c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
50
+ distance = R * c
51
+ return distance
52
+
53
+
54
+ class SplitReason(str, Enum):
55
+ time = "time"
56
+ distance = "distance"
57
+
58
+
59
+ @dataclass
60
+ class Split:
61
+ prevPic: Picture
62
+ nextPic: Picture
63
+ reason: SplitReason
64
+
65
+
66
+ @dataclass
67
+ class Sequence:
68
+ pictures: List[Picture]
69
+
70
+ def from_ts(self) -> Optional[datetime.datetime]:
71
+ """Start date/time of this sequence"""
72
+
73
+ if len(self.pictures) == 0:
74
+ return None
75
+ return self.pictures[0].metadata.ts
76
+
77
+ def to_ts(self) -> Optional[datetime.datetime]:
78
+ """End date/time of this sequence"""
79
+
80
+ if len(self.pictures) == 0:
81
+ return None
82
+ return self.pictures[-1].metadata.ts
83
+
84
+ def delta_with(self, otherSeq) -> Optional[Tuple[datetime.timedelta, float]]:
85
+ """
86
+ Delta between the end of this sequence and the start of the other one.
87
+ Returns a tuple (timedelta, distance in meters)
88
+ """
89
+
90
+ if len(self.pictures) == 0 or len(otherSeq.pictures) == 0:
91
+ return None
92
+
93
+ return (otherSeq.from_ts() - self.to_ts(), otherSeq.pictures[0].distance_to(self.pictures[-1])) # type: ignore
94
+
95
+
96
+ @dataclass
97
+ class DispatchReport:
98
+ sequences: List[Sequence]
99
+ duplicate_pictures: Optional[List[Picture]] = None
100
+ sequences_splits: Optional[List[Split]] = None
101
+
102
+
103
+ def sort_pictures(pictures: List[Picture], method: Optional[SortMethod] = SortMethod.time_asc) -> List[Picture]:
104
+ """Sorts pictures according to given strategy
105
+
106
+ Parameters
107
+ ----------
108
+ pictures : Picture[]
109
+ List of pictures to sort
110
+ method : SortMethod
111
+ Sort logic to adopt (time-asc, time-desc, filename-asc, filename-desc)
112
+
113
+ Returns
114
+ -------
115
+ Picture[]
116
+ List of pictures, sorted
117
+ """
118
+
119
+ if method is None:
120
+ method = SortMethod.time_asc
121
+
122
+ if method not in [item.value for item in SortMethod]:
123
+ raise Exception("Invalid sort strategy: " + str(method))
124
+
125
+ # Get the sort logic
126
+ strat, order = method.split("-")
127
+
128
+ # Sort based on filename
129
+ if strat == "filename":
130
+ # Check if pictures can be sorted by numeric notation
131
+ hasNonNumber = False
132
+ for p in pictures:
133
+ try:
134
+ int(PurePath(p.filename or "").stem)
135
+ except:
136
+ hasNonNumber = True
137
+ break
138
+
139
+ def sort_fct(p):
140
+ return PurePath(p.filename or "").stem if hasNonNumber else int(PurePath(p.filename or "").stem)
141
+
142
+ pictures.sort(key=sort_fct)
143
+
144
+ # Sort based on picture ts
145
+ elif strat == "time":
146
+ pictures.sort(key=lambda p: p.metadata.ts.isoformat() if p.metadata is not None else "0000-00-00T00:00:00Z")
147
+
148
+ if order == "desc":
149
+ pictures.reverse()
150
+
151
+ return pictures
152
+
153
+
154
+ def find_duplicates(pictures: List[Picture], params: Optional[MergeParams] = None) -> Tuple[List[Picture], List[Picture]]:
155
+ """
156
+ Finds too similar pictures.
157
+ Note that input list should be properly sorted.
158
+
159
+ Parameters
160
+ ----------
161
+ pictures : list of sorted pictures to check
162
+ params : parameters used to consider two pictures as a duplicate
163
+
164
+ Returns
165
+ -------
166
+ (Non-duplicates pictures, Duplicates pictures)
167
+ """
168
+
169
+ if params is None or not params.is_merge_needed():
170
+ return (pictures, [])
171
+
172
+ nonDups: List[Picture] = []
173
+ dups: List[Picture] = []
174
+ lastNonDuplicatedPicId = 0
175
+
176
+ for i, currentPic in enumerate(pictures):
177
+ if i == 0:
178
+ nonDups.append(currentPic)
179
+ continue
180
+
181
+ prevPic = pictures[lastNonDuplicatedPicId]
182
+
183
+ if prevPic.metadata is None or currentPic.metadata is None:
184
+ nonDups.append(currentPic)
185
+ continue
186
+
187
+ # Compare distance
188
+ dist = prevPic.distance_to(currentPic)
189
+
190
+ if dist <= params.maxDistance: # type: ignore
191
+ # Compare angle (if available on both images)
192
+ if params.maxRotationAngle is not None and prevPic.metadata.heading is not None and currentPic.metadata.heading is not None:
193
+ deltaAngle = abs(currentPic.metadata.heading - prevPic.metadata.heading)
194
+
195
+ if deltaAngle <= params.maxRotationAngle:
196
+ dups.append(currentPic)
197
+ else:
198
+ lastNonDuplicatedPicId = i
199
+ nonDups.append(currentPic)
200
+ else:
201
+ dups.append(currentPic)
202
+ else:
203
+ lastNonDuplicatedPicId = i
204
+ nonDups.append(currentPic)
205
+
206
+ return (nonDups, dups)
207
+
208
+
209
+ def split_in_sequences(pictures: List[Picture], splitParams: Optional[SplitParams] = SplitParams()) -> Tuple[List[Sequence], List[Split]]:
210
+ """
211
+ Split a list of pictures into many sequences.
212
+ Note that this function expect pictures to be sorted and have their metadata set.
213
+
214
+ Parameters
215
+ ----------
216
+ pictures : Picture[]
217
+ List of pictures to check, sorted and with metadata defined
218
+ splitParams : SplitParams
219
+ The parameters to define deltas between two pictures
220
+
221
+ Returns
222
+ -------
223
+ List[Sequence]
224
+ List of pictures splitted into smaller sequences
225
+ """
226
+
227
+ # No split parameters given -> just return given pictures
228
+ if splitParams is None or not splitParams.is_split_needed():
229
+ return ([Sequence(pictures)], [])
230
+
231
+ sequences: List[Sequence] = []
232
+ splits: List[Split] = []
233
+ currentPicList: List[Picture] = []
234
+
235
+ for pic in pictures:
236
+ if len(currentPicList) == 0: # No checks for 1st pic
237
+ currentPicList.append(pic)
238
+ else:
239
+ lastPic = currentPicList[-1]
240
+
241
+ # Missing metadata -> skip
242
+ if lastPic.metadata is None or pic.metadata is None:
243
+ currentPicList.append(pic)
244
+ continue
245
+
246
+ # Time delta
247
+ timeOutOfDelta = (
248
+ False if splitParams.maxTime is None else (abs(lastPic.metadata.ts - pic.metadata.ts)).total_seconds() > splitParams.maxTime
249
+ )
250
+
251
+ # Distance delta
252
+ distance = lastPic.distance_to(pic)
253
+ distanceOutOfDelta = False if splitParams.maxDistance is None else distance > splitParams.maxDistance
254
+
255
+ # One of deltas maxed -> create new sequence
256
+ if timeOutOfDelta or distanceOutOfDelta:
257
+ sequences.append(Sequence(currentPicList))
258
+ currentPicList = [pic]
259
+ splits.append(Split(lastPic, pic, SplitReason.time if timeOutOfDelta else SplitReason.distance))
260
+
261
+ # Otherwise, still in same sequence
262
+ else:
263
+ currentPicList.append(pic)
264
+
265
+ sequences.append(Sequence(currentPicList))
266
+
267
+ return (sequences, splits)
268
+
269
+
270
+ def dispatch_pictures(
271
+ pictures: List[Picture],
272
+ sortMethod: Optional[SortMethod] = None,
273
+ mergeParams: Optional[MergeParams] = None,
274
+ splitParams: Optional[SplitParams] = None,
275
+ ) -> DispatchReport:
276
+ """
277
+ Dispatches a set of pictures into many sequences.
278
+ This function both sorts, de-duplicates and splits in sequences all your pictures.
279
+
280
+ Parameters
281
+ ----------
282
+ pictures : set of pictures to dispatch
283
+ sortMethod : strategy for sorting pictures
284
+ mergeParams : conditions for considering two pictures as duplicates
285
+ splitParams : conditions for considering two sequences as distinct
286
+
287
+ Returns
288
+ -------
289
+ DispatchReport : clean sequences, duplicates pictures and split reasons
290
+ """
291
+
292
+ # Sort
293
+ myPics = sort_pictures(pictures, sortMethod)
294
+
295
+ # De-duplicate
296
+ (myPics, dupsPics) = find_duplicates(myPics, mergeParams)
297
+
298
+ # Split in sequences
299
+ (mySeqs, splits) = split_in_sequences(myPics, splitParams)
300
+
301
+ return DispatchReport(mySeqs, dupsPics if len(dupsPics) > 0 else None, splits if len(splits) > 0 else None)
@@ -0,0 +1,165 @@
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
+ msgid ""
7
+ msgstr ""
8
+ "Project-Id-Version: PACKAGE VERSION\n"
9
+ "Report-Msgid-Bugs-To: \n"
10
+ "POT-Creation-Date: 2024-07-10 13:05+0200\n"
11
+ "PO-Revision-Date: 2024-07-13 20:42+0000\n"
12
+ "Last-Translator: Bastian Greshake Tzovaras <bastian@gedankenstuecke.de>\n"
13
+ "Language-Team: German <http://weblate.panoramax.xyz/projects/panoramax/"
14
+ "tag-reader/de/>\n"
15
+ "Language: de\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/main.py:26
23
+ msgid "Latitude:"
24
+ msgstr "Breitengrad:"
25
+
26
+ #: geopic_tag_reader/main.py:27
27
+ msgid "Longitude:"
28
+ msgstr "Längengrad:"
29
+
30
+ #: geopic_tag_reader/main.py:28
31
+ msgid "Timestamp:"
32
+ msgstr "Zeitpunkt:"
33
+
34
+ #: geopic_tag_reader/main.py:29
35
+ msgid "Heading:"
36
+ msgstr "Richtung:"
37
+
38
+ #: geopic_tag_reader/main.py:30
39
+ msgid "Type:"
40
+ msgstr "Typ:"
41
+
42
+ #: geopic_tag_reader/main.py:31
43
+ msgid "Make:"
44
+ msgstr "Hersteller:"
45
+
46
+ #: geopic_tag_reader/main.py:32
47
+ msgid "Model:"
48
+ msgstr "Modell:"
49
+
50
+ #: geopic_tag_reader/main.py:33
51
+ msgid "Focal length:"
52
+ msgstr "Brennweite:"
53
+
54
+ #: geopic_tag_reader/main.py:34
55
+ msgid "Crop parameters:"
56
+ msgstr "Zuschnittseinstellungen:"
57
+
58
+ #: geopic_tag_reader/main.py:35
59
+ msgid "Pitch:"
60
+ msgstr "Neigung:"
61
+
62
+ #: geopic_tag_reader/main.py:36
63
+ msgid "Roll:"
64
+ msgstr "Rollwinkel:"
65
+
66
+ #: geopic_tag_reader/main.py:39
67
+ msgid "Warnings raised by reader:"
68
+ msgstr "Warnungen vom Leser erhoben:"
69
+
70
+ #: geopic_tag_reader/reader.py:183
71
+ msgid "Read latitude is out of WGS84 bounds (should be in [-90, 90])"
72
+ msgstr ""
73
+ "Ausgelesener Breitengrad liegt außerhalb der WGS84-Grenzen (sollte in "
74
+ "[-90,90] liegen)"
75
+
76
+ #: geopic_tag_reader/reader.py:185
77
+ msgid "Read longitude is out of WGS84 bounds (should be in [-180, 180])"
78
+ msgstr ""
79
+ "Ausgelesener Längengrad liegt außerhalb der WGS84-Grenzen (sollte in "
80
+ "[-180,180] liegen)"
81
+
82
+ #: geopic_tag_reader/reader.py:227
83
+ #, python-brace-format
84
+ msgid "Skipping Mapillary date/time as it was not recognized: {v}"
85
+ msgstr ""
86
+ "Mapillary Datum/Uhrzeit wurde übersprungen weil sie nicht erkannt wurden: {v}"
87
+
88
+ #: geopic_tag_reader/reader.py:240
89
+ msgid "Contradicting heading values found, GPSImgDirection value is used"
90
+ msgstr ""
91
+ "Widersprüchliche Richtungswerte gefunden, GPSImgDirection-Wert wird verwendet"
92
+
93
+ #: geopic_tag_reader/reader.py:351
94
+ msgid "No GPS coordinates or broken coordinates in picture EXIF tags"
95
+ msgstr "Keine oder invalide GPS-Koordinaten in den EXIF-Daten des Fotos"
96
+
97
+ #: geopic_tag_reader/reader.py:357
98
+ msgid "No valid date in picture EXIF tags"
99
+ msgstr "Kein valides Datum in den EXIF-Daten des Fotos"
100
+
101
+ #: geopic_tag_reader/reader.py:362
102
+ msgid "The picture is missing mandatory metadata:"
103
+ msgstr "Dem Foto fehlen obligatorische Metadaten:"
104
+
105
+ #: geopic_tag_reader/reader.py:449 geopic_tag_reader/reader.py:478
106
+ msgid "GPSLatitudeRef not found, assuming GPSLatitudeRef is North"
107
+ msgstr "GPSLatitudeRef nicht gefunden, nutze Norden als GPSLatitudeRef"
108
+
109
+ #: geopic_tag_reader/reader.py:457
110
+ msgid "Broken GPS coordinates in picture EXIF tags"
111
+ msgstr "Invalide GPS-Koordinaten in den EXIF-Daten des Fotos"
112
+
113
+ #: geopic_tag_reader/reader.py:460 geopic_tag_reader/reader.py:484
114
+ msgid "GPSLongitudeRef not found, assuming GPSLongitudeRef is East"
115
+ msgstr "GPSLongitudeRef nicht gefunden, nutze Osten als GPSLongitudeRef"
116
+
117
+ #: geopic_tag_reader/reader.py:537
118
+ msgid "Precise timezone information not found, fallback to UTC"
119
+ msgstr "Genaue Zeitzonen-Information nicht gefunden, falle auf UTC zurück"
120
+
121
+ #: geopic_tag_reader/reader.py:542
122
+ msgid ""
123
+ "Precise timezone information not found (and no GPS coordinates to help), "
124
+ "fallback to UTC"
125
+ msgstr ""
126
+ "Genaue Zeitzonen-Information nicht gefunden (und keine GPS-Koordinaten um zu "
127
+ "helfen), falle auf UTC zurück"
128
+
129
+ #: geopic_tag_reader/reader.py:546
130
+ #, python-brace-format
131
+ msgid ""
132
+ "Skipping original date/time (from {datefield}) as it was not recognized: {v}"
133
+ msgstr ""
134
+ "Überspringe originales Datum/Zeit (aus {datefield}) weil es nicht erkannt "
135
+ "wurde: {v}"
136
+
137
+ #: geopic_tag_reader/reader.py:580
138
+ #, python-brace-format
139
+ msgid ""
140
+ "GPSTimeStamp and GPSDateTime don't contain supported time format (in {group} "
141
+ "group)"
142
+ msgstr ""
143
+ "GPSTimeStamp und GPSDateTime enthalten kein unterstütztes Zeitformat (in "
144
+ "{group} Gruppe)"
145
+
146
+ #: geopic_tag_reader/reader.py:611
147
+ #, python-brace-format
148
+ msgid "Skipping GPS date/time ({group} group) as it was not recognized: {v}"
149
+ msgstr ""
150
+ "Überspringe GPS Datum/Zeit ({group} Gruppe) da es nicht erkannt wurde: {v}"
151
+
152
+ #: geopic_tag_reader/reader.py:637
153
+ #, python-brace-format
154
+ msgid ""
155
+ "Microseconds read from decimal seconds value ({microsecondsFromSeconds}) is "
156
+ "not matching value from EXIF field ({microseconds}). Max value will be kept."
157
+ msgstr ""
158
+ "Die Mikrosekunden die aus den Dezimalwerten der Sekunden gelesen wurden "
159
+ "({microsecondsFromSeconds}) stimmen nicht mit den Werten aus den EXIF-Daten "
160
+ "({microseconds}) überein. Maximaler Wert wird beibehalten."
161
+
162
+ #: geopic_tag_reader/writer.py:132
163
+ #, python-brace-format
164
+ msgid "Unsupported key in additional tags ({k})"
165
+ msgstr "Nicht unterstützter Schlüssel in den zusätzlichen Attributen ({k})"
@@ -7,8 +7,8 @@ msgid ""
7
7
  msgstr ""
8
8
  "Project-Id-Version: PACKAGE VERSION\n"
9
9
  "Report-Msgid-Bugs-To: \n"
10
- "POT-Creation-Date: 2024-07-10 10:35+0200\n"
11
- "PO-Revision-Date: 2024-07-10 10:35+0200\n"
10
+ "POT-Creation-Date: 2024-07-30 16:49+0200\n"
11
+ "PO-Revision-Date: 2024-07-30 16:49+0200\n"
12
12
  "Last-Translator: Automatically generated\n"
13
13
  "Language-Team: none\n"
14
14
  "Language: en\n"
@@ -61,56 +61,56 @@ msgstr "Pitch:"
61
61
  msgid "Roll:"
62
62
  msgstr "Roll:"
63
63
 
64
- #: geopic_tag_reader/main.py:39
64
+ #: geopic_tag_reader/main.py:37
65
+ msgid "Yaw:"
66
+ msgstr "Yaw:"
67
+
68
+ #: geopic_tag_reader/main.py:40
65
69
  msgid "Warnings raised by reader:"
66
70
  msgstr "Warnings raised by reader:"
67
71
 
68
- #: geopic_tag_reader/reader.py:183
72
+ #: geopic_tag_reader/reader.py:186
69
73
  msgid "Read latitude is out of WGS84 bounds (should be in [-90, 90])"
70
74
  msgstr "Read latitude is out of WGS84 bounds (should be in [-90, 90])"
71
75
 
72
- #: geopic_tag_reader/reader.py:185
76
+ #: geopic_tag_reader/reader.py:188
73
77
  msgid "Read longitude is out of WGS84 bounds (should be in [-180, 180])"
74
78
  msgstr "Read longitude is out of WGS84 bounds (should be in [-180, 180])"
75
79
 
76
- #: geopic_tag_reader/reader.py:227
80
+ #: geopic_tag_reader/reader.py:230
77
81
  #, python-brace-format
78
82
  msgid "Skipping Mapillary date/time as it was not recognized: {v}"
79
83
  msgstr "Skipping Mapillary date/time as it was not recognized: {v}"
80
84
 
81
- #: geopic_tag_reader/reader.py:240
82
- msgid "Contradicting heading values found, GPSImgDirection value is used"
83
- msgstr "Contradicting heading values found, GPSImgDirection value is used"
84
-
85
- #: geopic_tag_reader/reader.py:351
85
+ #: geopic_tag_reader/reader.py:337
86
86
  msgid "No GPS coordinates or broken coordinates in picture EXIF tags"
87
87
  msgstr "No GPS coordinates or broken coordinates in picture EXIF tags"
88
88
 
89
- #: geopic_tag_reader/reader.py:357
89
+ #: geopic_tag_reader/reader.py:343
90
90
  msgid "No valid date in picture EXIF tags"
91
91
  msgstr "No valid date in picture EXIF tags"
92
92
 
93
- #: geopic_tag_reader/reader.py:362
93
+ #: geopic_tag_reader/reader.py:348
94
94
  msgid "The picture is missing mandatory metadata:"
95
95
  msgstr "The picture is missing mandatory metadata:"
96
96
 
97
- #: geopic_tag_reader/reader.py:449 geopic_tag_reader/reader.py:478
97
+ #: geopic_tag_reader/reader.py:437 geopic_tag_reader/reader.py:466
98
98
  msgid "GPSLatitudeRef not found, assuming GPSLatitudeRef is North"
99
99
  msgstr "GPSLatitudeRef not found, assuming GPSLatitudeRef is North"
100
100
 
101
- #: geopic_tag_reader/reader.py:457
101
+ #: geopic_tag_reader/reader.py:445
102
102
  msgid "Broken GPS coordinates in picture EXIF tags"
103
103
  msgstr "Broken GPS coordinates in picture EXIF tags"
104
104
 
105
- #: geopic_tag_reader/reader.py:460 geopic_tag_reader/reader.py:484
105
+ #: geopic_tag_reader/reader.py:448 geopic_tag_reader/reader.py:472
106
106
  msgid "GPSLongitudeRef not found, assuming GPSLongitudeRef is East"
107
107
  msgstr "GPSLongitudeRef not found, assuming GPSLongitudeRef is East"
108
108
 
109
- #: geopic_tag_reader/reader.py:537
109
+ #: geopic_tag_reader/reader.py:525
110
110
  msgid "Precise timezone information not found, fallback to UTC"
111
111
  msgstr "Precise timezone information not found, fallback to UTC"
112
112
 
113
- #: geopic_tag_reader/reader.py:542
113
+ #: geopic_tag_reader/reader.py:530
114
114
  msgid ""
115
115
  "Precise timezone information not found (and no GPS coordinates to help), "
116
116
  "fallback to UTC"
@@ -118,14 +118,14 @@ msgstr ""
118
118
  "Precise timezone information not found (and no GPS coordinates to help), "
119
119
  "fallback to UTC"
120
120
 
121
- #: geopic_tag_reader/reader.py:546
121
+ #: geopic_tag_reader/reader.py:534
122
122
  #, python-brace-format
123
123
  msgid ""
124
124
  "Skipping original date/time (from {datefield}) as it was not recognized: {v}"
125
125
  msgstr ""
126
126
  "Skipping original date/time (from {datefield}) as it was not recognized: {v}"
127
127
 
128
- #: geopic_tag_reader/reader.py:580
128
+ #: geopic_tag_reader/reader.py:568
129
129
  #, python-brace-format
130
130
  msgid ""
131
131
  "GPSTimeStamp and GPSDateTime don't contain supported time format (in {group} "
@@ -134,12 +134,12 @@ msgstr ""
134
134
  "GPSTimeStamp and GPSDateTime don't contain supported time format (in {group} "
135
135
  "group)"
136
136
 
137
- #: geopic_tag_reader/reader.py:611
137
+ #: geopic_tag_reader/reader.py:599
138
138
  #, python-brace-format
139
139
  msgid "Skipping GPS date/time ({group} group) as it was not recognized: {v}"
140
140
  msgstr "Skipping GPS date/time ({group} group) as it was not recognized: {v}"
141
141
 
142
- #: geopic_tag_reader/reader.py:637
142
+ #: geopic_tag_reader/reader.py:625
143
143
  #, python-brace-format
144
144
  msgid ""
145
145
  "Microseconds read from decimal seconds value ({microsecondsFromSeconds}) is "
@@ -3,18 +3,17 @@
3
3
  # This file is distributed under the same license as the PACKAGE package.
4
4
  # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
5
  #
6
- #, fuzzy
7
6
  msgid ""
8
7
  msgstr ""
9
8
  "Project-Id-Version: PACKAGE VERSION\n"
10
9
  "Report-Msgid-Bugs-To: \n"
11
- "POT-Creation-Date: 2024-07-10 10:35+0200\n"
10
+ "POT-Creation-Date: 2024-07-10 13:05+0200\n"
12
11
  "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"
12
+ "Last-Translator: Automatically generated\n"
13
+ "Language-Team: none\n"
14
+ "Language: es\n"
16
15
  "MIME-Version: 1.0\n"
17
- "Content-Type: text/plain; charset=CHARSET\n"
16
+ "Content-Type: text/plain; charset=UTF-8\n"
18
17
  "Content-Transfer-Encoding: 8bit\n"
19
18
 
20
19
  #: geopic_tag_reader/main.py:26
@@ -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-30 16:49+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:37
65
+ msgid "Yaw:"
66
+ msgstr ""
67
+
68
+ #: geopic_tag_reader/main.py:40
69
+ msgid "Warnings raised by reader:"
70
+ msgstr ""
71
+
72
+ #: geopic_tag_reader/reader.py:186
73
+ msgid "Read latitude is out of WGS84 bounds (should be in [-90, 90])"
74
+ msgstr ""
75
+
76
+ #: geopic_tag_reader/reader.py:188
77
+ msgid "Read longitude is out of WGS84 bounds (should be in [-180, 180])"
78
+ msgstr ""
79
+
80
+ #: geopic_tag_reader/reader.py:230
81
+ #, python-brace-format
82
+ msgid "Skipping Mapillary date/time as it was not recognized: {v}"
83
+ msgstr ""
84
+
85
+ #: geopic_tag_reader/reader.py:337
86
+ msgid "No GPS coordinates or broken coordinates in picture EXIF tags"
87
+ msgstr ""
88
+
89
+ #: geopic_tag_reader/reader.py:343
90
+ msgid "No valid date in picture EXIF tags"
91
+ msgstr ""
92
+
93
+ #: geopic_tag_reader/reader.py:348
94
+ msgid "The picture is missing mandatory metadata:"
95
+ msgstr ""
96
+
97
+ #: geopic_tag_reader/reader.py:437 geopic_tag_reader/reader.py:466
98
+ msgid "GPSLatitudeRef not found, assuming GPSLatitudeRef is North"
99
+ msgstr ""
100
+
101
+ #: geopic_tag_reader/reader.py:445
102
+ msgid "Broken GPS coordinates in picture EXIF tags"
103
+ msgstr ""
104
+
105
+ #: geopic_tag_reader/reader.py:448 geopic_tag_reader/reader.py:472
106
+ msgid "GPSLongitudeRef not found, assuming GPSLongitudeRef is East"
107
+ msgstr ""
108
+
109
+ #: geopic_tag_reader/reader.py:525
110
+ msgid "Precise timezone information not found, fallback to UTC"
111
+ msgstr ""
112
+
113
+ #: geopic_tag_reader/reader.py:530
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:534
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:568
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:599
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:625
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 ""
@@ -49,6 +49,7 @@ requires = ["flit_core ~= 3.8,<4"]
49
49
  build-backend = "flit_core.buildapi"
50
50
 
51
51
  [tool.flit.sdist]
52
+ include = ["geopic_tag_reader/translations/**/*.mo"]
52
53
  exclude = ["tests"]
53
54
 
54
55
  [tool.pytest.ini_options]