mapillary-tools 0.13.3a1__py3-none-any.whl → 0.14.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. mapillary_tools/__init__.py +1 -1
  2. mapillary_tools/api_v4.py +287 -22
  3. mapillary_tools/authenticate.py +326 -64
  4. mapillary_tools/blackvue_parser.py +195 -0
  5. mapillary_tools/camm/camm_builder.py +55 -97
  6. mapillary_tools/camm/camm_parser.py +429 -181
  7. mapillary_tools/commands/__main__.py +17 -8
  8. mapillary_tools/commands/authenticate.py +8 -1
  9. mapillary_tools/commands/process.py +27 -51
  10. mapillary_tools/commands/process_and_upload.py +19 -5
  11. mapillary_tools/commands/sample_video.py +2 -3
  12. mapillary_tools/commands/upload.py +44 -13
  13. mapillary_tools/commands/video_process_and_upload.py +19 -5
  14. mapillary_tools/config.py +65 -26
  15. mapillary_tools/constants.py +141 -18
  16. mapillary_tools/exceptions.py +37 -34
  17. mapillary_tools/exif_read.py +221 -116
  18. mapillary_tools/exif_write.py +10 -8
  19. mapillary_tools/exiftool_read.py +33 -42
  20. mapillary_tools/exiftool_read_video.py +97 -47
  21. mapillary_tools/exiftool_runner.py +57 -0
  22. mapillary_tools/ffmpeg.py +417 -242
  23. mapillary_tools/geo.py +158 -118
  24. mapillary_tools/geotag/__init__.py +0 -1
  25. mapillary_tools/geotag/base.py +147 -0
  26. mapillary_tools/geotag/factory.py +307 -0
  27. mapillary_tools/geotag/geotag_images_from_exif.py +14 -131
  28. mapillary_tools/geotag/geotag_images_from_exiftool.py +136 -85
  29. mapillary_tools/geotag/geotag_images_from_gpx.py +60 -124
  30. mapillary_tools/geotag/geotag_images_from_gpx_file.py +13 -126
  31. mapillary_tools/geotag/geotag_images_from_nmea_file.py +4 -5
  32. mapillary_tools/geotag/geotag_images_from_video.py +88 -51
  33. mapillary_tools/geotag/geotag_videos_from_exiftool.py +123 -0
  34. mapillary_tools/geotag/geotag_videos_from_gpx.py +52 -0
  35. mapillary_tools/geotag/geotag_videos_from_video.py +20 -185
  36. mapillary_tools/geotag/image_extractors/base.py +18 -0
  37. mapillary_tools/geotag/image_extractors/exif.py +60 -0
  38. mapillary_tools/geotag/image_extractors/exiftool.py +18 -0
  39. mapillary_tools/geotag/options.py +182 -0
  40. mapillary_tools/geotag/utils.py +52 -16
  41. mapillary_tools/geotag/video_extractors/base.py +18 -0
  42. mapillary_tools/geotag/video_extractors/exiftool.py +70 -0
  43. mapillary_tools/geotag/video_extractors/gpx.py +116 -0
  44. mapillary_tools/geotag/video_extractors/native.py +160 -0
  45. mapillary_tools/{geotag → gpmf}/gpmf_parser.py +205 -182
  46. mapillary_tools/{geotag → gpmf}/gps_filter.py +5 -3
  47. mapillary_tools/history.py +134 -20
  48. mapillary_tools/mp4/construct_mp4_parser.py +17 -10
  49. mapillary_tools/mp4/io_utils.py +0 -1
  50. mapillary_tools/mp4/mp4_sample_parser.py +36 -28
  51. mapillary_tools/mp4/simple_mp4_builder.py +10 -9
  52. mapillary_tools/mp4/simple_mp4_parser.py +13 -22
  53. mapillary_tools/process_geotag_properties.py +184 -414
  54. mapillary_tools/process_sequence_properties.py +594 -225
  55. mapillary_tools/sample_video.py +20 -26
  56. mapillary_tools/serializer/description.py +587 -0
  57. mapillary_tools/serializer/gpx.py +132 -0
  58. mapillary_tools/telemetry.py +26 -13
  59. mapillary_tools/types.py +98 -611
  60. mapillary_tools/upload.py +408 -416
  61. mapillary_tools/upload_api_v4.py +172 -174
  62. mapillary_tools/uploader.py +804 -284
  63. mapillary_tools/utils.py +49 -18
  64. {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0.dist-info}/METADATA +93 -35
  65. mapillary_tools-0.14.0.dist-info/RECORD +75 -0
  66. {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0.dist-info}/WHEEL +1 -1
  67. mapillary_tools/geotag/blackvue_parser.py +0 -118
  68. mapillary_tools/geotag/geotag_from_generic.py +0 -22
  69. mapillary_tools/geotag/geotag_images_from_exiftool_both_image_and_video.py +0 -93
  70. mapillary_tools/geotag/geotag_videos_from_exiftool_video.py +0 -145
  71. mapillary_tools/video_data_extraction/cli_options.py +0 -22
  72. mapillary_tools/video_data_extraction/extract_video_data.py +0 -176
  73. mapillary_tools/video_data_extraction/extractors/base_parser.py +0 -75
  74. mapillary_tools/video_data_extraction/extractors/blackvue_parser.py +0 -34
  75. mapillary_tools/video_data_extraction/extractors/camm_parser.py +0 -38
  76. mapillary_tools/video_data_extraction/extractors/exiftool_runtime_parser.py +0 -71
  77. mapillary_tools/video_data_extraction/extractors/exiftool_xml_parser.py +0 -53
  78. mapillary_tools/video_data_extraction/extractors/generic_video_parser.py +0 -52
  79. mapillary_tools/video_data_extraction/extractors/gopro_parser.py +0 -43
  80. mapillary_tools/video_data_extraction/extractors/gpx_parser.py +0 -108
  81. mapillary_tools/video_data_extraction/extractors/nmea_parser.py +0 -24
  82. mapillary_tools/video_data_extraction/video_data_parser_factory.py +0 -39
  83. mapillary_tools-0.13.3a1.dist-info/RECORD +0 -75
  84. /mapillary_tools/{geotag → gpmf}/gpmf_gps_filter.py +0 -0
  85. {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0.dist-info}/entry_points.txt +0 -0
  86. {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0.dist-info/licenses}/LICENSE +0 -0
  87. {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,9 @@
1
+ from __future__ import annotations
2
+
1
3
  import io
2
4
  import typing as T
3
5
 
4
- from .. import geo, telemetry, types
6
+ from .. import geo
5
7
  from ..mp4 import (
6
8
  construct_mp4_parser as cparser,
7
9
  mp4_sample_parser as sample_parser,
@@ -11,84 +13,35 @@ from ..mp4 import (
11
13
  from . import camm_parser
12
14
 
13
15
 
14
- TelemetryMeasurement = T.Union[
15
- geo.Point,
16
- telemetry.TelemetryMeasurement,
17
- ]
16
+ def _build_camm_sample(measurement: camm_parser.TelemetryMeasurement) -> bytes:
17
+ if camm_parser.GoProGPSSampleEntry.serializable(measurement):
18
+ return camm_parser.GoProGPSSampleEntry.serialize(measurement)
18
19
 
20
+ for sample_entry_cls in camm_parser.SAMPLE_ENTRY_CLS_BY_CAMM_TYPE.values():
21
+ if sample_entry_cls.serializable(measurement):
22
+ return sample_entry_cls.serialize(measurement)
19
23
 
20
- def _build_camm_sample(measurement: TelemetryMeasurement) -> bytes:
21
- if isinstance(measurement, geo.Point):
22
- return camm_parser.CAMMSampleData.build(
23
- {
24
- "type": camm_parser.CAMMType.MIN_GPS.value,
25
- "data": [
26
- measurement.lat,
27
- measurement.lon,
28
- -1.0 if measurement.alt is None else measurement.alt,
29
- ],
30
- }
31
- )
32
- elif isinstance(measurement, telemetry.AccelerationData):
33
- # Accelerometer reading in meters/second^2 along XYZ axes of the camera.
34
- return camm_parser.CAMMSampleData.build(
35
- {
36
- "type": camm_parser.CAMMType.ACCELERATION.value,
37
- "data": [
38
- measurement.x,
39
- measurement.y,
40
- measurement.z,
41
- ],
42
- }
43
- )
44
- elif isinstance(measurement, telemetry.GyroscopeData):
45
- # Gyroscope signal in radians/seconds around XYZ axes of the camera. Rotation is positive in the counterclockwise direction.
46
- return camm_parser.CAMMSampleData.build(
47
- {
48
- "type": camm_parser.CAMMType.GYRO.value,
49
- "data": [
50
- measurement.x,
51
- measurement.y,
52
- measurement.z,
53
- ],
54
- }
55
- )
56
- elif isinstance(measurement, telemetry.MagnetometerData):
57
- # Ambient magnetic field.
58
- return camm_parser.CAMMSampleData.build(
59
- {
60
- "type": camm_parser.CAMMType.MAGNETIC_FIELD.value,
61
- "data": [
62
- measurement.x,
63
- measurement.y,
64
- measurement.z,
65
- ],
66
- }
67
- )
68
- else:
69
- raise ValueError(f"unexpected measurement type {type(measurement)}")
24
+ raise ValueError(f"Unsupported measurement type {type(measurement)}")
70
25
 
71
26
 
72
27
  def _create_edit_list_from_points(
73
- point_segments: T.Sequence[T.Sequence[geo.Point]],
28
+ tracks: T.Sequence[T.Sequence[geo.Point]],
74
29
  movie_timescale: int,
75
30
  media_timescale: int,
76
31
  ) -> builder.BoxDict:
77
- entries: T.List[T.Dict] = []
32
+ entries: list[dict] = []
78
33
 
79
- non_empty_point_segments = [points for points in point_segments if points]
34
+ non_empty_tracks = [track for track in tracks if track]
80
35
 
81
- for idx, points in enumerate(non_empty_point_segments):
82
- assert 0 <= points[0].time, (
83
- f"expect non-negative point time but got {points[0]}"
84
- )
85
- assert points[0].time <= points[-1].time, (
86
- f"expect points to be sorted but got first point {points[0]} and last point {points[-1]}"
36
+ for idx, track in enumerate(non_empty_tracks):
37
+ assert 0 <= track[0].time, f"expect non-negative point time but got {track[0]}"
38
+ assert track[0].time <= track[-1].time, (
39
+ f"expect points to be sorted but got first point {track[0]} and last point {track[-1]}"
87
40
  )
88
41
 
89
42
  if idx == 0:
90
- if 0 < points[0].time:
91
- segment_duration = int(points[0].time * movie_timescale)
43
+ if 0 < track[0].time:
44
+ segment_duration = int(track[0].time * movie_timescale)
92
45
  # put an empty edit list entry to skip the initial gap
93
46
  entries.append(
94
47
  {
@@ -100,8 +53,8 @@ def _create_edit_list_from_points(
100
53
  }
101
54
  )
102
55
  else:
103
- media_time = int(points[0].time * media_timescale)
104
- segment_duration = int((points[-1].time - points[0].time) * movie_timescale)
56
+ media_time = int(track[0].time * media_timescale)
57
+ segment_duration = int((track[-1].time - track[0].time) * movie_timescale)
105
58
  entries.append(
106
59
  {
107
60
  "media_time": media_time,
@@ -119,18 +72,8 @@ def _create_edit_list_from_points(
119
72
  }
120
73
 
121
74
 
122
- def _multiplex(
123
- points: T.Sequence[geo.Point],
124
- measurements: T.Optional[T.List[telemetry.TelemetryMeasurement]] = None,
125
- ) -> T.List[TelemetryMeasurement]:
126
- mutiplexed: T.List[TelemetryMeasurement] = [*points, *(measurements or [])]
127
- mutiplexed.sort(key=lambda m: m.time)
128
-
129
- return mutiplexed
130
-
131
-
132
75
  def convert_telemetry_to_raw_samples(
133
- measurements: T.Sequence[TelemetryMeasurement],
76
+ measurements: T.Sequence[camm_parser.TelemetryMeasurement],
134
77
  timescale: int,
135
78
  ) -> T.Generator[sample_parser.RawSample, None, None]:
136
79
  for idx, measurement in enumerate(measurements):
@@ -281,29 +224,44 @@ def create_camm_trak(
281
224
  }
282
225
 
283
226
 
284
- def camm_sample_generator2(
285
- video_metadata: types.VideoMetadata,
286
- telemetry_measurements: T.Optional[T.List[telemetry.TelemetryMeasurement]] = None,
287
- ):
227
+ def camm_sample_generator2(camm_info: camm_parser.CAMMInfo):
288
228
  def _f(
289
229
  fp: T.BinaryIO,
290
- moov_children: T.List[builder.BoxDict],
230
+ moov_children: list[builder.BoxDict],
291
231
  ) -> T.Generator[io.IOBase, None, None]:
292
232
  movie_timescale = builder.find_movie_timescale(moov_children)
293
- # make sure the precision of timedeltas not lower than 0.001 (1ms)
233
+ # Make sure the precision of timedeltas not lower than 0.001 (1ms)
294
234
  media_timescale = max(1000, movie_timescale)
295
235
 
296
- # points with negative time are skipped
297
- # TODO: interpolate first point at time == 0
298
- # TODO: measurements with negative times should be skipped too
299
- points = [point for point in video_metadata.points if point.time >= 0]
300
-
301
- measurements = _multiplex(points, telemetry_measurements)
236
+ # Multiplex points for creating elst
237
+ track: list[geo.Point] = [
238
+ *(camm_info.gps or []),
239
+ *(camm_info.mini_gps or []),
240
+ ]
241
+ track.sort(key=lambda p: p.time)
242
+ if track and track[0].time < 0:
243
+ track = [p for p in track if p.time >= 0]
244
+ elst = _create_edit_list_from_points([track], movie_timescale, media_timescale)
245
+
246
+ # Multiplex telemetry measurements
247
+ measurements: list[camm_parser.TelemetryMeasurement] = [
248
+ *(camm_info.gps or []),
249
+ *(camm_info.mini_gps or []),
250
+ *(camm_info.accl or []),
251
+ *(camm_info.gyro or []),
252
+ *(camm_info.magn or []),
253
+ ]
254
+ measurements.sort(key=lambda m: m.time)
255
+ if measurements and measurements[0].time < 0:
256
+ measurements = [m for m in measurements if m.time >= 0]
257
+
258
+ # Serialize the telemetry measurements into MP4 samples
302
259
  camm_samples = list(
303
260
  convert_telemetry_to_raw_samples(measurements, media_timescale)
304
261
  )
262
+
305
263
  camm_trak = create_camm_trak(camm_samples, media_timescale)
306
- elst = _create_edit_list_from_points([points], movie_timescale, media_timescale)
264
+
307
265
  if T.cast(T.Dict, elst["data"])["entries"]:
308
266
  T.cast(T.List[builder.BoxDict], camm_trak["data"]).append(
309
267
  {
@@ -313,19 +271,19 @@ def camm_sample_generator2(
313
271
  )
314
272
  moov_children.append(camm_trak)
315
273
 
316
- udta_data: T.List[builder.BoxDict] = []
317
- if video_metadata.make:
274
+ udta_data: list[builder.BoxDict] = []
275
+ if camm_info.make:
318
276
  udta_data.append(
319
277
  {
320
278
  "type": b"@mak",
321
- "data": video_metadata.make.encode("utf-8"),
279
+ "data": camm_info.make.encode("utf-8"),
322
280
  }
323
281
  )
324
- if video_metadata.model:
282
+ if camm_info.model:
325
283
  udta_data.append(
326
284
  {
327
285
  "type": b"@mod",
328
- "data": video_metadata.model.encode("utf-8"),
286
+ "data": camm_info.model.encode("utf-8"),
329
287
  }
330
288
  )
331
289
  if udta_data: