mapillary-tools 0.14.0a2__py3-none-any.whl → 0.14.1__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.
Files changed (49) hide show
  1. mapillary_tools/__init__.py +1 -1
  2. mapillary_tools/api_v4.py +66 -262
  3. mapillary_tools/authenticate.py +54 -46
  4. mapillary_tools/blackvue_parser.py +79 -22
  5. mapillary_tools/commands/__main__.py +15 -16
  6. mapillary_tools/commands/upload.py +33 -4
  7. mapillary_tools/config.py +38 -17
  8. mapillary_tools/constants.py +127 -43
  9. mapillary_tools/exceptions.py +4 -0
  10. mapillary_tools/exif_read.py +2 -1
  11. mapillary_tools/exif_write.py +3 -1
  12. mapillary_tools/exiftool_read_video.py +52 -15
  13. mapillary_tools/exiftool_runner.py +4 -24
  14. mapillary_tools/ffmpeg.py +406 -232
  15. mapillary_tools/geo.py +16 -0
  16. mapillary_tools/geotag/__init__.py +0 -0
  17. mapillary_tools/geotag/base.py +8 -4
  18. mapillary_tools/geotag/factory.py +106 -89
  19. mapillary_tools/geotag/geotag_images_from_exiftool.py +27 -20
  20. mapillary_tools/geotag/geotag_images_from_gpx.py +7 -6
  21. mapillary_tools/geotag/geotag_images_from_video.py +35 -0
  22. mapillary_tools/geotag/geotag_videos_from_exiftool.py +61 -14
  23. mapillary_tools/geotag/geotag_videos_from_gpx.py +22 -9
  24. mapillary_tools/geotag/options.py +25 -3
  25. mapillary_tools/geotag/utils.py +9 -12
  26. mapillary_tools/geotag/video_extractors/base.py +1 -1
  27. mapillary_tools/geotag/video_extractors/exiftool.py +1 -1
  28. mapillary_tools/geotag/video_extractors/gpx.py +61 -70
  29. mapillary_tools/geotag/video_extractors/native.py +34 -31
  30. mapillary_tools/history.py +128 -8
  31. mapillary_tools/http.py +211 -0
  32. mapillary_tools/mp4/construct_mp4_parser.py +8 -2
  33. mapillary_tools/process_geotag_properties.py +47 -35
  34. mapillary_tools/process_sequence_properties.py +340 -325
  35. mapillary_tools/sample_video.py +8 -8
  36. mapillary_tools/serializer/description.py +587 -0
  37. mapillary_tools/serializer/gpx.py +132 -0
  38. mapillary_tools/types.py +44 -610
  39. mapillary_tools/upload.py +327 -352
  40. mapillary_tools/upload_api_v4.py +125 -72
  41. mapillary_tools/uploader.py +797 -216
  42. mapillary_tools/utils.py +57 -5
  43. {mapillary_tools-0.14.0a2.dist-info → mapillary_tools-0.14.1.dist-info}/METADATA +91 -34
  44. mapillary_tools-0.14.1.dist-info/RECORD +76 -0
  45. {mapillary_tools-0.14.0a2.dist-info → mapillary_tools-0.14.1.dist-info}/WHEEL +1 -1
  46. mapillary_tools-0.14.0a2.dist-info/RECORD +0 -72
  47. {mapillary_tools-0.14.0a2.dist-info → mapillary_tools-0.14.1.dist-info}/entry_points.txt +0 -0
  48. {mapillary_tools-0.14.0a2.dist-info → mapillary_tools-0.14.1.dist-info}/licenses/LICENSE +0 -0
  49. {mapillary_tools-0.14.0a2.dist-info → mapillary_tools-0.14.1.dist-info}/top_level.txt +0 -0
@@ -82,6 +82,32 @@ def _extract_alternative_fields(
82
82
  return None
83
83
 
84
84
 
85
+ def _same_gps_point(left: GPSPoint, right: GPSPoint) -> bool:
86
+ """
87
+ >>> left = GPSPoint(time=56.0, lat=36.741385, lon=29.021274, alt=141.6, angle=1.54, epoch_time=None, fix=None, precision=None, ground_speed=None)
88
+ >>> right = GPSPoint(time=56.0, lat=36.741385, lon=29.021274, alt=142.4, angle=1.54, epoch_time=None, fix=None, precision=None, ground_speed=None)
89
+ >>> _same_gps_point(left, right)
90
+ True
91
+ """
92
+ return (
93
+ left.time == right.time
94
+ and left.lon == right.lon
95
+ and left.lat == right.lat
96
+ and left.epoch_time == right.epoch_time
97
+ and left.angle == right.angle
98
+ )
99
+
100
+
101
+ def _deduplicate_gps_points(
102
+ track: list[GPSPoint], same_gps_point: T.Callable[[GPSPoint, GPSPoint], bool]
103
+ ) -> list[GPSPoint]:
104
+ deduplicated_track: list[GPSPoint] = []
105
+ for point in track:
106
+ if not deduplicated_track or not same_gps_point(deduplicated_track[-1], point):
107
+ deduplicated_track.append(point)
108
+ return deduplicated_track
109
+
110
+
85
111
  def _aggregate_gps_track(
86
112
  texts_by_tag: dict[str, list[str]],
87
113
  time_tag: str | None,
@@ -174,7 +200,7 @@ def _aggregate_gps_track(
174
200
  epoch_time = geo.as_unix_time(dt)
175
201
 
176
202
  # build track
177
- track = []
203
+ track: list[GPSPoint] = []
178
204
  for timestamp, lon, lat, alt, direction, ground_speed in zip(
179
205
  timestamps,
180
206
  lons,
@@ -185,22 +211,26 @@ def _aggregate_gps_track(
185
211
  ):
186
212
  if timestamp is None or lon is None or lat is None:
187
213
  continue
188
- track.append(
189
- GPSPoint(
190
- time=timestamp,
191
- lon=lon,
192
- lat=lat,
193
- alt=alt,
194
- angle=direction,
195
- epoch_time=epoch_time,
196
- fix=None,
197
- precision=None,
198
- ground_speed=ground_speed,
199
- )
214
+
215
+ point = GPSPoint(
216
+ time=timestamp,
217
+ lon=lon,
218
+ lat=lat,
219
+ alt=alt,
220
+ angle=direction,
221
+ epoch_time=epoch_time,
222
+ fix=None,
223
+ precision=None,
224
+ ground_speed=ground_speed,
200
225
  )
201
226
 
227
+ if not track or not _same_gps_point(track[-1], point):
228
+ track.append(point)
229
+
202
230
  track.sort(key=lambda point: point.time)
203
231
 
232
+ track = _deduplicate_gps_points(track, same_gps_point=_same_gps_point)
233
+
204
234
  if time_tag is not None:
205
235
  if track:
206
236
  first_time = track[0].time
@@ -310,7 +340,10 @@ class ExifToolReadVideo:
310
340
  etree: ET.ElementTree,
311
341
  ) -> None:
312
342
  self.etree = etree
313
- self._texts_by_tag = _index_text_by_tag(self.etree.getroot())
343
+ root = self.etree.getroot()
344
+ if root is None:
345
+ raise ValueError("ElementTree root is None")
346
+ self._texts_by_tag = _index_text_by_tag(root)
314
347
  self._all_tags = set(self._texts_by_tag.keys())
315
348
 
316
349
  def extract_gps_track(self) -> list[geo.Point]:
@@ -371,6 +404,10 @@ class ExifToolReadVideo:
371
404
  return model
372
405
 
373
406
  def _extract_gps_track_from_track(self) -> list[GPSPoint]:
407
+ root = self.etree.getroot()
408
+ if root is None:
409
+ raise ValueError("ElementTree root is None")
410
+
374
411
  for track_id in range(1, MAX_TRACK_ID + 1):
375
412
  track_ns = f"Track{track_id}"
376
413
  if self._all_tags_exists(
@@ -382,7 +419,7 @@ class ExifToolReadVideo:
382
419
  }
383
420
  ):
384
421
  sample_iterator = _aggregate_samples(
385
- self.etree.getroot(),
422
+ root,
386
423
  f"{track_ns}:SampleTime",
387
424
  f"{track_ns}:SampleDuration",
388
425
  )
@@ -1,7 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import platform
4
- import shutil
5
3
  import subprocess
6
4
  import typing as T
7
5
  from pathlib import Path
@@ -12,32 +10,14 @@ class ExiftoolRunner:
12
10
  Wrapper around ExifTool to run it in a subprocess
13
11
  """
14
12
 
15
- def __init__(self, exiftool_path: str | None = None, recursive: bool = False):
16
- if exiftool_path is None:
17
- exiftool_path = self._search_preferred_exiftool_path()
18
- self.exiftool_path = exiftool_path
13
+ def __init__(self, exiftool_executable: str = "exiftool", recursive: bool = False):
14
+ self.exiftool_executable = exiftool_executable
19
15
  self.recursive = recursive
20
16
 
21
- def _search_preferred_exiftool_path(self) -> str:
22
- system = platform.system()
23
-
24
- if system and system.lower() == "windows":
25
- exiftool_paths = ["exiftool.exe", "exiftool"]
26
- else:
27
- exiftool_paths = ["exiftool", "exiftool.exe"]
28
-
29
- for path in exiftool_paths:
30
- full_path = shutil.which(path)
31
- if full_path:
32
- return path
33
-
34
- # Always return the prefered one, even if it is not found,
35
- # and let the subprocess.run figure out the error later
36
- return exiftool_paths[0]
37
-
38
17
  def _build_args_read_stdin(self) -> list[str]:
39
18
  args: list[str] = [
40
- self.exiftool_path,
19
+ self.exiftool_executable,
20
+ "-fast",
41
21
  "-q",
42
22
  "-n", # Disable print conversion
43
23
  "-X", # XML output