mapillary-tools 0.12.1__py3-none-any.whl → 0.13.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 (59) hide show
  1. mapillary_tools/__init__.py +1 -1
  2. mapillary_tools/api_v4.py +94 -4
  3. mapillary_tools/{geotag → camm}/camm_builder.py +73 -61
  4. mapillary_tools/camm/camm_parser.py +561 -0
  5. mapillary_tools/commands/__init__.py +0 -1
  6. mapillary_tools/commands/__main__.py +0 -6
  7. mapillary_tools/commands/process.py +0 -50
  8. mapillary_tools/commands/upload.py +1 -26
  9. mapillary_tools/constants.py +2 -2
  10. mapillary_tools/exiftool_read_video.py +13 -11
  11. mapillary_tools/ffmpeg.py +2 -2
  12. mapillary_tools/geo.py +0 -54
  13. mapillary_tools/geotag/blackvue_parser.py +4 -4
  14. mapillary_tools/geotag/geotag_images_from_exif.py +2 -1
  15. mapillary_tools/geotag/geotag_images_from_exiftool_both_image_and_video.py +0 -1
  16. mapillary_tools/geotag/geotag_images_from_gpx_file.py +7 -1
  17. mapillary_tools/geotag/geotag_videos_from_exiftool_video.py +5 -3
  18. mapillary_tools/geotag/geotag_videos_from_video.py +13 -14
  19. mapillary_tools/geotag/gpmf_gps_filter.py +9 -10
  20. mapillary_tools/geotag/gpmf_parser.py +346 -83
  21. mapillary_tools/mp4/__init__.py +0 -0
  22. mapillary_tools/{geotag → mp4}/construct_mp4_parser.py +32 -16
  23. mapillary_tools/mp4/mp4_sample_parser.py +322 -0
  24. mapillary_tools/{geotag → mp4}/simple_mp4_builder.py +64 -38
  25. mapillary_tools/process_geotag_properties.py +25 -19
  26. mapillary_tools/process_sequence_properties.py +6 -6
  27. mapillary_tools/sample_video.py +17 -16
  28. mapillary_tools/telemetry.py +71 -0
  29. mapillary_tools/types.py +18 -0
  30. mapillary_tools/upload.py +74 -233
  31. mapillary_tools/upload_api_v4.py +8 -9
  32. mapillary_tools/utils.py +9 -16
  33. mapillary_tools/video_data_extraction/cli_options.py +0 -1
  34. mapillary_tools/video_data_extraction/extract_video_data.py +13 -31
  35. mapillary_tools/video_data_extraction/extractors/base_parser.py +13 -11
  36. mapillary_tools/video_data_extraction/extractors/blackvue_parser.py +5 -4
  37. mapillary_tools/video_data_extraction/extractors/camm_parser.py +13 -16
  38. mapillary_tools/video_data_extraction/extractors/exiftool_runtime_parser.py +4 -9
  39. mapillary_tools/video_data_extraction/extractors/exiftool_xml_parser.py +9 -11
  40. mapillary_tools/video_data_extraction/extractors/generic_video_parser.py +6 -11
  41. mapillary_tools/video_data_extraction/extractors/gopro_parser.py +11 -4
  42. mapillary_tools/video_data_extraction/extractors/gpx_parser.py +90 -11
  43. mapillary_tools/video_data_extraction/extractors/nmea_parser.py +3 -3
  44. mapillary_tools/video_data_extraction/video_data_parser_factory.py +13 -20
  45. {mapillary_tools-0.12.1.dist-info → mapillary_tools-0.13.1.dist-info}/METADATA +10 -3
  46. mapillary_tools-0.13.1.dist-info/RECORD +75 -0
  47. {mapillary_tools-0.12.1.dist-info → mapillary_tools-0.13.1.dist-info}/WHEEL +1 -1
  48. mapillary_tools/commands/upload_blackvue.py +0 -33
  49. mapillary_tools/commands/upload_camm.py +0 -33
  50. mapillary_tools/commands/upload_zip.py +0 -33
  51. mapillary_tools/geotag/camm_parser.py +0 -306
  52. mapillary_tools/geotag/mp4_sample_parser.py +0 -426
  53. mapillary_tools/process_import_meta_properties.py +0 -76
  54. mapillary_tools-0.12.1.dist-info/RECORD +0 -77
  55. /mapillary_tools/{geotag → mp4}/io_utils.py +0 -0
  56. /mapillary_tools/{geotag → mp4}/simple_mp4_parser.py +0 -0
  57. {mapillary_tools-0.12.1.dist-info → mapillary_tools-0.13.1.dist-info}/LICENSE +0 -0
  58. {mapillary_tools-0.12.1.dist-info → mapillary_tools-0.13.1.dist-info}/entry_points.txt +0 -0
  59. {mapillary_tools-0.12.1.dist-info → mapillary_tools-0.13.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,561 @@
1
+ # pyre-ignore-all-errors[5, 11, 16, 21, 24, 58]
2
+
3
+ import abc
4
+ import dataclasses
5
+ import io
6
+ import logging
7
+ import pathlib
8
+ import typing as T
9
+ from enum import Enum
10
+
11
+ import construct as C
12
+
13
+ from .. import geo, telemetry
14
+ from ..mp4 import simple_mp4_parser as sparser
15
+ from ..mp4.mp4_sample_parser import MovieBoxParser, Sample, TrackBoxParser
16
+
17
+
18
+ LOG = logging.getLogger(__name__)
19
+
20
+
21
+ TelemetryMeasurement = T.Union[
22
+ geo.Point,
23
+ telemetry.TelemetryMeasurement,
24
+ ]
25
+
26
+
27
+ # Camera Motion Metadata Spec https://developers.google.com/streetview/publish/camm-spec
28
+ class CAMMType(Enum):
29
+ ANGLE_AXIS = 0
30
+ EXPOSURE_TIME = 1
31
+ GYRO = 2
32
+ ACCELERATION = 3
33
+ POSITION = 4
34
+ MIN_GPS = 5
35
+ GPS = 6
36
+ MAGNETIC_FIELD = 7
37
+
38
+ # Mapillary extensions are offset by 1024
39
+ # GoPro GPS is not compatible with CAMMType.GPS,
40
+ # so we use a new type to represent it
41
+ MLY_GOPRO_GPS = 1024 + 6
42
+
43
+
44
+ # All fields are little-endian
45
+ Float = C.Float32l
46
+ Double = C.Float64l
47
+
48
+
49
+ TTelemetry = T.TypeVar("TTelemetry", bound=TelemetryMeasurement)
50
+
51
+
52
+ class CAMMSampleEntry(abc.ABC, T.Generic[TTelemetry]):
53
+ camm_type: CAMMType
54
+
55
+ construct: C.Struct
56
+
57
+ telemetry_cls: T.Type[TTelemetry]
58
+
59
+ @classmethod
60
+ def serializable(cls, data: T.Any, throw: bool = False) -> bool:
61
+ # Use "is" for exact type match, instead of isinstance
62
+ if type(data) is cls.telemetry_cls:
63
+ return True
64
+
65
+ if throw:
66
+ raise TypeError(
67
+ f"{cls} can not serialize {type(data)}: expect {cls.telemetry_cls}"
68
+ )
69
+ return False
70
+
71
+ @classmethod
72
+ @abc.abstractmethod
73
+ def serialize(cls, data: TTelemetry) -> bytes:
74
+ raise NotImplementedError
75
+
76
+ @classmethod
77
+ @abc.abstractmethod
78
+ def deserialize(cls, sample: Sample, data: T.Any) -> TTelemetry:
79
+ raise NotImplementedError
80
+
81
+
82
+ class MinGPSSampleEntry(CAMMSampleEntry):
83
+ camm_type = CAMMType.MIN_GPS
84
+
85
+ construct = Double[3] # type: ignore
86
+
87
+ telemetry_cls = geo.Point
88
+
89
+ @classmethod
90
+ def deserialize(cls, sample: Sample, data: T.Any) -> geo.Point:
91
+ return geo.Point(
92
+ time=sample.exact_time,
93
+ lat=data[0],
94
+ lon=data[1],
95
+ alt=data[2],
96
+ angle=None,
97
+ )
98
+
99
+ @classmethod
100
+ def serialize(cls, data: geo.Point) -> bytes:
101
+ cls.serializable(data, throw=True)
102
+
103
+ return CAMMSampleData.build(
104
+ {
105
+ "type": cls.camm_type.value,
106
+ "data": [
107
+ data.lat,
108
+ data.lon,
109
+ -1.0 if data.alt is None else data.alt,
110
+ ],
111
+ }
112
+ )
113
+
114
+
115
+ class GPSSampleEntry(CAMMSampleEntry):
116
+ camm_type: CAMMType = CAMMType.GPS
117
+
118
+ construct = C.Struct(
119
+ "time_gps_epoch" / Double, # type: ignore
120
+ "gps_fix_type" / C.Int32sl, # type: ignore
121
+ "latitude" / Double, # type: ignore
122
+ "longitude" / Double, # type: ignore
123
+ "altitude" / Float, # type: ignore
124
+ "horizontal_accuracy" / Float, # type: ignore
125
+ "vertical_accuracy" / Float, # type: ignore
126
+ "velocity_east" / Float, # type: ignore
127
+ "velocity_north" / Float, # type: ignore
128
+ "velocity_up" / Float, # type: ignore
129
+ "speed_accuracy" / Float, # type: ignore
130
+ )
131
+
132
+ telemetry_cls = telemetry.CAMMGPSPoint
133
+
134
+ @classmethod
135
+ def deserialize(cls, sample: Sample, data: T.Any) -> telemetry.CAMMGPSPoint:
136
+ return telemetry.CAMMGPSPoint(
137
+ time=sample.exact_time,
138
+ lat=data.latitude,
139
+ lon=data.longitude,
140
+ alt=data.altitude,
141
+ angle=None,
142
+ time_gps_epoch=data.time_gps_epoch,
143
+ gps_fix_type=data.gps_fix_type,
144
+ horizontal_accuracy=data.horizontal_accuracy,
145
+ vertical_accuracy=data.vertical_accuracy,
146
+ velocity_east=data.velocity_east,
147
+ velocity_north=data.velocity_north,
148
+ velocity_up=data.velocity_up,
149
+ speed_accuracy=data.speed_accuracy,
150
+ )
151
+
152
+ @classmethod
153
+ def serialize(cls, data: telemetry.CAMMGPSPoint) -> bytes:
154
+ cls.serializable(data, throw=True)
155
+
156
+ return CAMMSampleData.build(
157
+ {
158
+ "type": cls.camm_type.value,
159
+ "data": {
160
+ "time_gps_epoch": data.time_gps_epoch,
161
+ "gps_fix_type": data.gps_fix_type,
162
+ "latitude": data.lat,
163
+ "longitude": data.lon,
164
+ "altitude": -1.0 if data.alt is None else data.alt,
165
+ "horizontal_accuracy": data.horizontal_accuracy,
166
+ "vertical_accuracy": data.vertical_accuracy,
167
+ "velocity_east": data.velocity_east,
168
+ "velocity_north": data.velocity_north,
169
+ "velocity_up": data.velocity_up,
170
+ "speed_accuracy": data.speed_accuracy,
171
+ },
172
+ }
173
+ )
174
+
175
+
176
+ class GoProGPSSampleEntry(CAMMSampleEntry):
177
+ camm_type: CAMMType = CAMMType.MLY_GOPRO_GPS
178
+
179
+ construct = C.Struct(
180
+ "latitude" / Double, # type: ignore
181
+ "longitude" / Double, # type: ignore
182
+ "altitude" / Float, # type: ignore
183
+ "epoch_time" / Double, # type: ignore
184
+ "fix" / C.Int32sl, # type: ignore
185
+ "precision" / Float, # type: ignore
186
+ "ground_speed" / Float, # type: ignore
187
+ )
188
+
189
+ telemetry_cls = telemetry.GPSPoint
190
+
191
+ @classmethod
192
+ def deserialize(cls, sample: Sample, data: T.Any) -> telemetry.GPSPoint:
193
+ return telemetry.GPSPoint(
194
+ time=sample.exact_time,
195
+ lat=data.latitude,
196
+ lon=data.longitude,
197
+ alt=data.altitude,
198
+ angle=None,
199
+ epoch_time=data.epoch_time,
200
+ fix=telemetry.GPSFix(data.fix),
201
+ precision=data.precision,
202
+ ground_speed=data.ground_speed,
203
+ )
204
+
205
+ @classmethod
206
+ def serialize(cls, data: telemetry.GPSPoint) -> bytes:
207
+ cls.serializable(data, throw=True)
208
+
209
+ if data.fix is None:
210
+ gps_fix = telemetry.GPSFix.NO_FIX.value
211
+ else:
212
+ gps_fix = data.fix.value
213
+
214
+ return CAMMSampleData.build(
215
+ {
216
+ "type": cls.camm_type.value,
217
+ "data": {
218
+ "latitude": data.lat,
219
+ "longitude": data.lon,
220
+ "altitude": -1.0 if data.alt is None else data.alt,
221
+ "epoch_time": data.epoch_time,
222
+ "fix": gps_fix,
223
+ "precision": data.precision,
224
+ "ground_speed": data.ground_speed,
225
+ },
226
+ }
227
+ )
228
+
229
+
230
+ class AccelerationSampleEntry(CAMMSampleEntry):
231
+ camm_type: CAMMType = CAMMType.ACCELERATION
232
+
233
+ construct: C.Struct = Float[3] # type: ignore
234
+
235
+ telemetry_cls = telemetry.AccelerationData
236
+
237
+ @classmethod
238
+ def deserialize(cls, sample: Sample, data: T.Any) -> telemetry.AccelerationData:
239
+ return telemetry.AccelerationData(
240
+ time=sample.exact_time,
241
+ x=data[0],
242
+ y=data[1],
243
+ z=data[2],
244
+ )
245
+
246
+ @classmethod
247
+ def serialize(cls, data: telemetry.AccelerationData) -> bytes:
248
+ cls.serializable(data, throw=True)
249
+
250
+ return CAMMSampleData.build(
251
+ {
252
+ "type": cls.camm_type.value,
253
+ "data": [data.x, data.y, data.z],
254
+ }
255
+ )
256
+
257
+
258
+ class GyroscopeSampleEntry(CAMMSampleEntry):
259
+ camm_type: CAMMType = CAMMType.GYRO
260
+
261
+ construct: C.Struct = Float[3] # type: ignore
262
+
263
+ telemetry_cls = telemetry.GyroscopeData
264
+
265
+ @classmethod
266
+ def deserialize(cls, sample: Sample, data: T.Any) -> telemetry.GyroscopeData:
267
+ return telemetry.GyroscopeData(
268
+ time=sample.exact_time,
269
+ x=data[0],
270
+ y=data[1],
271
+ z=data[2],
272
+ )
273
+
274
+ @classmethod
275
+ def serialize(cls, data: telemetry.GyroscopeData) -> bytes:
276
+ cls.serializable(data)
277
+
278
+ return CAMMSampleData.build(
279
+ {
280
+ "type": cls.camm_type.value,
281
+ "data": [data.x, data.y, data.z],
282
+ }
283
+ )
284
+
285
+
286
+ class MagnetometerSampleEntry(CAMMSampleEntry):
287
+ camm_type: CAMMType = CAMMType.MAGNETIC_FIELD
288
+
289
+ construct: C.Struct = Float[3] # type: ignore
290
+
291
+ telemetry_cls = telemetry.MagnetometerData
292
+
293
+ @classmethod
294
+ def deserialize(cls, sample: Sample, data: T.Any) -> telemetry.MagnetometerData:
295
+ return telemetry.MagnetometerData(
296
+ time=sample.exact_time,
297
+ x=data[0],
298
+ y=data[1],
299
+ z=data[2],
300
+ )
301
+
302
+ @classmethod
303
+ def serialize(cls, data: telemetry.MagnetometerData) -> bytes:
304
+ cls.serializable(data)
305
+
306
+ return CAMMSampleData.build(
307
+ {
308
+ "type": cls.camm_type.value,
309
+ "data": [data.x, data.y, data.z],
310
+ }
311
+ )
312
+
313
+
314
+ SAMPLE_ENTRY_CLS_BY_CAMM_TYPE = {
315
+ sample_entry_cls.camm_type: sample_entry_cls
316
+ for sample_entry_cls in CAMMSampleEntry.__subclasses__()
317
+ }
318
+ assert len(SAMPLE_ENTRY_CLS_BY_CAMM_TYPE) == 6, SAMPLE_ENTRY_CLS_BY_CAMM_TYPE.keys()
319
+
320
+
321
+ _SWITCH: T.Dict[int, C.Struct] = {
322
+ # angle_axis
323
+ CAMMType.ANGLE_AXIS.value: Float[3], # type: ignore
324
+ CAMMType.EXPOSURE_TIME.value: C.Struct(
325
+ "pixel_exposure_time" / C.Int32sl, # type: ignore
326
+ "rolling_shutter_skew_time" / C.Int32sl, # type: ignore
327
+ ),
328
+ # position
329
+ CAMMType.POSITION.value: Float[3], # type: ignore
330
+ **{t.value: cls.construct for t, cls in SAMPLE_ENTRY_CLS_BY_CAMM_TYPE.items()},
331
+ }
332
+
333
+ CAMMSampleData = C.Struct(
334
+ C.Padding(2),
335
+ "type" / C.Int16ul,
336
+ "data" / C.Switch(C.this.type, _SWITCH),
337
+ )
338
+
339
+
340
+ def _parse_telemetry_from_sample(
341
+ fp: T.BinaryIO, sample: Sample
342
+ ) -> T.Optional[TelemetryMeasurement]:
343
+ fp.seek(sample.raw_sample.offset, io.SEEK_SET)
344
+ data = fp.read(sample.raw_sample.size)
345
+ box = CAMMSampleData.parse(data)
346
+
347
+ camm_type = CAMMType(box.type) # type: ignore
348
+ SampleKlass = SAMPLE_ENTRY_CLS_BY_CAMM_TYPE.get(camm_type)
349
+ if SampleKlass is None:
350
+ return None
351
+ return SampleKlass.deserialize(sample, box.data)
352
+
353
+
354
+ def _filter_telemetry_by_elst_segments(
355
+ measurements: T.Iterable[TelemetryMeasurement],
356
+ elst: T.Sequence[T.Tuple[float, float]],
357
+ ) -> T.Generator[TelemetryMeasurement, None, None]:
358
+ empty_elst = [entry for entry in elst if entry[0] == -1]
359
+ if empty_elst:
360
+ offset = empty_elst[-1][1]
361
+ else:
362
+ offset = 0
363
+
364
+ elst = [entry for entry in elst if entry[0] != -1]
365
+
366
+ if not elst:
367
+ for m in measurements:
368
+ yield dataclasses.replace(m, time=m.time + offset)
369
+ return
370
+
371
+ elst.sort(key=lambda entry: entry[0])
372
+ elst_idx = 0
373
+ for m in measurements:
374
+ if len(elst) <= elst_idx:
375
+ break
376
+ media_time, duration = elst[elst_idx]
377
+ if m.time < media_time:
378
+ pass
379
+ elif m.time <= media_time + duration:
380
+ yield dataclasses.replace(m, time=m.time + offset)
381
+ else:
382
+ elst_idx += 1
383
+
384
+
385
+ def elst_entry_to_seconds(
386
+ entry: T.Dict, movie_timescale: int, media_timescale: int
387
+ ) -> T.Tuple[float, float]:
388
+ assert movie_timescale > 0, "expected positive movie_timescale"
389
+ assert media_timescale > 0, "expected positive media_timescale"
390
+ media_time, duration = entry["media_time"], entry["segment_duration"]
391
+ if media_time != -1:
392
+ media_time = media_time / media_timescale
393
+ duration = duration / movie_timescale
394
+ return (media_time, duration)
395
+
396
+
397
+ def _is_camm_description(description: T.Dict) -> bool:
398
+ return description["format"] == b"camm"
399
+
400
+
401
+ def _contains_camm_description(track: TrackBoxParser) -> bool:
402
+ descriptions = track.extract_sample_descriptions()
403
+ return any(_is_camm_description(d) for d in descriptions)
404
+
405
+
406
+ def _filter_telemetry_by_track_elst(
407
+ moov: MovieBoxParser,
408
+ track: TrackBoxParser,
409
+ measurements: T.Iterable[TelemetryMeasurement],
410
+ ) -> T.List[TelemetryMeasurement]:
411
+ elst_boxdata = track.extract_elst_boxdata()
412
+
413
+ if elst_boxdata is not None:
414
+ elst_entries = elst_boxdata["entries"]
415
+ if elst_entries:
416
+ # media_timescale
417
+ mdhd_boxdata = track.extract_mdhd_boxdata()
418
+ media_timescale = mdhd_boxdata["timescale"]
419
+
420
+ # movie_timescale
421
+ mvhd_boxdata = moov.extract_mvhd_boxdata()
422
+ movie_timescale = mvhd_boxdata["timescale"]
423
+
424
+ segments = [
425
+ elst_entry_to_seconds(
426
+ entry,
427
+ movie_timescale=movie_timescale,
428
+ media_timescale=media_timescale,
429
+ )
430
+ for entry in elst_entries
431
+ ]
432
+
433
+ return list(_filter_telemetry_by_elst_segments(measurements, segments))
434
+
435
+ return list(measurements)
436
+
437
+
438
+ def extract_points(fp: T.BinaryIO) -> T.Optional[T.List[geo.Point]]:
439
+ """
440
+ Return a list of points (could be empty) if it is a valid CAMM video,
441
+ otherwise None
442
+ """
443
+
444
+ moov = MovieBoxParser.parse_stream(fp)
445
+
446
+ for track in moov.extract_tracks():
447
+ if _contains_camm_description(track):
448
+ maybe_measurements = (
449
+ _parse_telemetry_from_sample(fp, sample)
450
+ for sample in track.extract_samples()
451
+ if _is_camm_description(sample.description)
452
+ )
453
+ points = [m for m in maybe_measurements if isinstance(m, geo.Point)]
454
+
455
+ return T.cast(
456
+ T.List[geo.Point], _filter_telemetry_by_track_elst(moov, track, points)
457
+ )
458
+
459
+ return None
460
+
461
+
462
+ def extract_telemetry_data(fp: T.BinaryIO) -> T.Optional[T.List[TelemetryMeasurement]]:
463
+ moov = MovieBoxParser.parse_stream(fp)
464
+
465
+ for track in moov.extract_tracks():
466
+ if _contains_camm_description(track):
467
+ maybe_measurements = (
468
+ _parse_telemetry_from_sample(fp, sample)
469
+ for sample in track.extract_samples()
470
+ if _is_camm_description(sample.description)
471
+ )
472
+ measurements = [m for m in maybe_measurements if m is not None]
473
+
474
+ measurements = _filter_telemetry_by_track_elst(moov, track, measurements)
475
+
476
+ return measurements
477
+
478
+ return None
479
+
480
+
481
+ def parse_gpx(path: pathlib.Path) -> T.List[geo.Point]:
482
+ with path.open("rb") as fp:
483
+ points = extract_points(fp)
484
+ if points is None:
485
+ return []
486
+ return points
487
+
488
+
489
+ MakeOrModel = C.Struct(
490
+ "size" / C.Int16ub,
491
+ C.Padding(2),
492
+ "data" / C.FixedSized(C.this.size, C.GreedyBytes),
493
+ )
494
+
495
+
496
+ def _decode_quietly(data: bytes, h: sparser.Header) -> str:
497
+ try:
498
+ return data.decode("utf-8")
499
+ except UnicodeDecodeError:
500
+ LOG.warning("Failed to decode %s: %s", h, data[:512])
501
+ return ""
502
+
503
+
504
+ def _parse_quietly(data: bytes, h: sparser.Header) -> bytes:
505
+ try:
506
+ parsed = MakeOrModel.parse(data)
507
+ except C.ConstructError:
508
+ LOG.warning("Failed to parse %s: %s", h, data[:512])
509
+ return b""
510
+ return parsed["data"]
511
+
512
+
513
+ def extract_camera_make_and_model(fp: T.BinaryIO) -> T.Tuple[str, str]:
514
+ header_and_stream = sparser.parse_path(
515
+ fp,
516
+ [
517
+ b"moov",
518
+ b"udta",
519
+ [
520
+ # Insta360 Titan
521
+ b"\xa9mak",
522
+ b"\xa9mod",
523
+ # RICHO THETA V
524
+ b"@mod",
525
+ b"@mak",
526
+ # RICHO THETA V
527
+ b"manu",
528
+ b"modl",
529
+ ],
530
+ ],
531
+ )
532
+
533
+ make: T.Optional[str] = None
534
+ model: T.Optional[str] = None
535
+
536
+ try:
537
+ for h, s in header_and_stream:
538
+ data = s.read(h.maxsize)
539
+ if h.type == b"\xa9mak":
540
+ make_data = _parse_quietly(data, h)
541
+ make_data = make_data.rstrip(b"\x00")
542
+ make = _decode_quietly(make_data, h)
543
+ elif h.type == b"\xa9mod":
544
+ model_data = _parse_quietly(data, h)
545
+ model_data = model_data.rstrip(b"\x00")
546
+ model = _decode_quietly(model_data, h)
547
+ elif h.type in [b"@mak", b"manu"]:
548
+ make = _decode_quietly(data, h)
549
+ elif h.type in [b"@mod", b"modl"]:
550
+ model = _decode_quietly(data, h)
551
+ # quit when both found
552
+ if make and model:
553
+ break
554
+ except sparser.ParsingError:
555
+ pass
556
+
557
+ if make:
558
+ make = make.strip()
559
+ if model:
560
+ model = model.strip()
561
+ return make or "", model or ""
@@ -5,7 +5,6 @@ from . import (
5
5
  process_and_upload,
6
6
  sample_video,
7
7
  upload,
8
- upload_camm,
9
8
  video_process,
10
9
  video_process_and_upload,
11
10
  )
@@ -12,9 +12,6 @@ from . import (
12
12
  process_and_upload,
13
13
  sample_video,
14
14
  upload,
15
- upload_blackvue,
16
- upload_camm,
17
- upload_zip,
18
15
  video_process,
19
16
  video_process_and_upload,
20
17
  zip,
@@ -23,9 +20,6 @@ from . import (
23
20
  mapillary_tools_commands = [
24
21
  process,
25
22
  upload,
26
- upload_camm,
27
- upload_blackvue,
28
- upload_zip,
29
23
  sample_video,
30
24
  video_process,
31
25
  authenticate,
@@ -10,7 +10,6 @@ from ..process_geotag_properties import (
10
10
  process_finalize,
11
11
  process_geotag_properties,
12
12
  )
13
- from ..process_import_meta_properties import process_import_meta_properties
14
13
  from ..process_sequence_properties import process_sequence_properties
15
14
 
16
15
 
@@ -107,44 +106,6 @@ class Command:
107
106
  default=None,
108
107
  required=False,
109
108
  )
110
- group_metadata.add_argument(
111
- "--add_file_name",
112
- help="[DEPRECATED since v0.9.4] Add original file name to EXIF.",
113
- action="store_true",
114
- required=False,
115
- )
116
- group_metadata.add_argument(
117
- "--add_import_date",
118
- help="[DEPRECATED since v0.10.0] Add import date.",
119
- action="store_true",
120
- required=False,
121
- )
122
- group_metadata.add_argument(
123
- "--orientation",
124
- help="Specify the image orientation in degrees. Note this might result in image rotation. Note this input has precedence over the input read from the import source file.",
125
- choices=[0, 90, 180, 270],
126
- type=int,
127
- default=None,
128
- required=False,
129
- )
130
- group_metadata.add_argument(
131
- "--GPS_accuracy",
132
- help="GPS accuracy in meters. Note this input has precedence over the input read from the import source file.",
133
- default=None,
134
- required=False,
135
- )
136
- group_metadata.add_argument(
137
- "--camera_uuid",
138
- help="Custom string used to differentiate different captures taken with the same camera make and model.",
139
- default=None,
140
- required=False,
141
- )
142
- group_metadata.add_argument(
143
- "--custom_meta_data",
144
- help='[DEPRECATED since v0.10.0] Add custom meta data to all images. Required format of input is a string, consisting of the meta data name, type and value, separated by a comma for each entry, where entries are separated by semicolon. Supported types are long, double, string, boolean, date. Example for two meta data entries "random_name1,double,12.34;random_name2,long,1234".',
145
- default=None,
146
- required=False,
147
- )
148
109
 
149
110
  group_geotagging = parser.add_argument_group(
150
111
  f"{constants.ANSI_BOLD}PROCESS GEOTAGGING OPTIONS{constants.ANSI_RESET_ALL}"
@@ -278,17 +239,6 @@ class Command:
278
239
  ),
279
240
  )
280
241
 
281
- metadatas = process_import_meta_properties(
282
- metadatas=metadatas,
283
- **(
284
- {
285
- k: v
286
- for k, v in vars_args.items()
287
- if k in inspect.getfullargspec(process_import_meta_properties).args
288
- }
289
- ),
290
- )
291
-
292
242
  metadatas = process_sequence_properties(
293
243
  metadatas=metadatas,
294
244
  **(
@@ -1,8 +1,7 @@
1
1
  import inspect
2
- import typing as T
3
2
 
4
3
  from .. import constants
5
- from ..upload import DirectUploadFileType, FileType, upload
4
+ from ..upload import upload
6
5
 
7
6
 
8
7
  class Command:
@@ -34,30 +33,6 @@ class Command:
34
33
  group = parser.add_argument_group(
35
34
  f"{constants.ANSI_BOLD}UPLOAD OPTIONS{constants.ANSI_RESET_ALL}"
36
35
  )
37
- default_filetypes = ",".join(sorted(t.value for t in FileType))
38
- supported_filetypes = ",".join(
39
- sorted(
40
- [t.value for t in DirectUploadFileType] + [t.value for t in FileType]
41
- )
42
- )
43
-
44
- def _type(option: str) -> T.List[T.Union[FileType, DirectUploadFileType]]:
45
- r: T.List[T.Union[FileType, DirectUploadFileType]] = []
46
- for t in option.split(","):
47
- if t in [x.value for x in FileType]:
48
- r.append(FileType(t))
49
- else:
50
- r.append(DirectUploadFileType(t))
51
- return r
52
-
53
- group.add_argument(
54
- "--filetypes",
55
- "--file_types",
56
- help=f"Upload files of the specified types only. Supported file types: {supported_filetypes} [default: %(default)s]",
57
- type=_type,
58
- default=default_filetypes,
59
- required=False,
60
- )
61
36
  group.add_argument(
62
37
  "--desc_path",
63
38
  help=f'Path to the description file generated by the process command. The hyphen "-" indicates STDIN. [default: {{IMPORT_PATH}}/{constants.IMAGE_DESCRIPTION_FILENAME}]',
@@ -43,8 +43,8 @@ GOPRO_GPS_PRECISION = float(os.getenv(_ENV_PREFIX + "GOPRO_GPS_PRECISION", 15))
43
43
 
44
44
  # WARNING: Changing the following envvars might result in failed uploads
45
45
  # Max number of images per sequence
46
- MAX_SEQUENCE_LENGTH = int(os.getenv(_ENV_PREFIX + "MAX_SEQUENCE_LENGTH", 500))
46
+ MAX_SEQUENCE_LENGTH = int(os.getenv(_ENV_PREFIX + "MAX_SEQUENCE_LENGTH", 1000))
47
47
  # Max file size per sequence (sum of image filesizes in the sequence)
48
- MAX_SEQUENCE_FILESIZE: str = os.getenv(_ENV_PREFIX + "MAX_SEQUENCE_FILESIZE", "2G")
48
+ MAX_SEQUENCE_FILESIZE: str = os.getenv(_ENV_PREFIX + "MAX_SEQUENCE_FILESIZE", "10G")
49
49
  # Max number of pixels per sequence (sum of image pixels in the sequence)
50
50
  MAX_SEQUENCE_PIXELS: str = os.getenv(_ENV_PREFIX + "MAX_SEQUENCE_PIXELS", "6G")