mapillary-tools 0.14.0b1__py3-none-any.whl → 0.14.2__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 (38) hide show
  1. mapillary_tools/__init__.py +1 -1
  2. mapillary_tools/api_v4.py +66 -263
  3. mapillary_tools/authenticate.py +47 -39
  4. mapillary_tools/commands/__main__.py +15 -16
  5. mapillary_tools/commands/upload.py +33 -4
  6. mapillary_tools/config.py +5 -0
  7. mapillary_tools/constants.py +127 -45
  8. mapillary_tools/exceptions.py +4 -0
  9. mapillary_tools/exif_read.py +2 -1
  10. mapillary_tools/exif_write.py +3 -1
  11. mapillary_tools/geo.py +16 -0
  12. mapillary_tools/geotag/base.py +6 -2
  13. mapillary_tools/geotag/factory.py +9 -1
  14. mapillary_tools/geotag/geotag_images_from_exiftool.py +1 -1
  15. mapillary_tools/geotag/geotag_images_from_gpx.py +0 -6
  16. mapillary_tools/geotag/geotag_videos_from_exiftool.py +30 -9
  17. mapillary_tools/geotag/options.py +4 -1
  18. mapillary_tools/geotag/utils.py +9 -12
  19. mapillary_tools/geotag/video_extractors/gpx.py +2 -1
  20. mapillary_tools/geotag/video_extractors/native.py +25 -0
  21. mapillary_tools/history.py +124 -7
  22. mapillary_tools/http.py +211 -0
  23. mapillary_tools/mp4/construct_mp4_parser.py +8 -2
  24. mapillary_tools/process_geotag_properties.py +35 -38
  25. mapillary_tools/process_sequence_properties.py +339 -322
  26. mapillary_tools/sample_video.py +1 -2
  27. mapillary_tools/serializer/description.py +68 -58
  28. mapillary_tools/serializer/gpx.py +1 -1
  29. mapillary_tools/upload.py +202 -207
  30. mapillary_tools/upload_api_v4.py +57 -47
  31. mapillary_tools/uploader.py +728 -285
  32. mapillary_tools/utils.py +57 -5
  33. {mapillary_tools-0.14.0b1.dist-info → mapillary_tools-0.14.2.dist-info}/METADATA +7 -6
  34. {mapillary_tools-0.14.0b1.dist-info → mapillary_tools-0.14.2.dist-info}/RECORD +38 -37
  35. {mapillary_tools-0.14.0b1.dist-info → mapillary_tools-0.14.2.dist-info}/WHEEL +0 -0
  36. {mapillary_tools-0.14.0b1.dist-info → mapillary_tools-0.14.2.dist-info}/entry_points.txt +0 -0
  37. {mapillary_tools-0.14.0b1.dist-info → mapillary_tools-0.14.2.dist-info}/licenses/LICENSE +0 -0
  38. {mapillary_tools-0.14.0b1.dist-info → mapillary_tools-0.14.2.dist-info}/top_level.txt +0 -0
@@ -125,12 +125,11 @@ def sample_video(
125
125
 
126
126
  except Exception as ex:
127
127
  if skip_sample_errors:
128
- exc_info = LOG.getEffectiveLevel() <= logging.DEBUG
129
128
  LOG.warning(
130
129
  "Skipping the error sampling %s: %s",
131
130
  video_path,
132
131
  str(ex),
133
- exc_info=exc_info,
132
+ exc_info=LOG.isEnabledFor(logging.DEBUG),
134
133
  )
135
134
  else:
136
135
  raise
@@ -86,22 +86,20 @@ class VideoDescription(_SharedDescription, total=False):
86
86
  MAPDeviceModel: str
87
87
 
88
88
 
89
- class _ErrorDescription(TypedDict, total=False):
90
- # type and message are required
89
+ class _ErrorObject(TypedDict, total=False):
91
90
  type: Required[str]
92
- message: str
93
- # vars is optional
91
+ message: Required[str]
94
92
  vars: dict
95
93
 
96
94
 
97
- class ImageDescriptionError(TypedDict, total=False):
95
+ class ErrorDescription(TypedDict, total=False):
98
96
  filename: Required[str]
99
- error: Required[_ErrorDescription]
97
+ error: Required[_ErrorObject]
100
98
  filetype: str
101
99
 
102
100
 
103
101
  Description = T.Union[ImageDescription, VideoDescription]
104
- DescriptionOrError = T.Union[ImageDescription, VideoDescription, ImageDescriptionError]
102
+ DescriptionOrError = T.Union[ImageDescription, VideoDescription, ErrorDescription]
105
103
 
106
104
 
107
105
  ImageDescriptionEXIFSchema = {
@@ -261,6 +259,11 @@ ImageDescriptionFileSchema = _merge_schema(
261
259
  )
262
260
 
263
261
 
262
+ ImageDescriptionFileSchemaValidator = jsonschema.Draft202012Validator(
263
+ ImageDescriptionFileSchema
264
+ )
265
+
266
+
264
267
  VideoDescriptionFileSchema = _merge_schema(
265
268
  VideoDescriptionSchema,
266
269
  {
@@ -297,6 +300,11 @@ VideoDescriptionFileSchema = _merge_schema(
297
300
  )
298
301
 
299
302
 
303
+ VideoDescriptionFileSchemaValidator = jsonschema.Draft202012Validator(
304
+ VideoDescriptionFileSchema
305
+ )
306
+
307
+
300
308
  ImageVideoDescriptionFileSchema = {
301
309
  "oneOf": [VideoDescriptionFileSchema, ImageDescriptionFileSchema]
302
310
  }
@@ -307,7 +315,7 @@ class DescriptionJSONSerializer(BaseSerializer):
307
315
  @classmethod
308
316
  def serialize(cls, metadatas: T.Sequence[MetadataOrError]) -> bytes:
309
317
  descs = [cls.as_desc(m) for m in metadatas]
310
- return json.dumps(descs).encode("utf-8")
318
+ return json.dumps(descs, sort_keys=True, separators=(",", ":")).encode("utf-8")
311
319
 
312
320
  @override
313
321
  @classmethod
@@ -327,7 +335,7 @@ class DescriptionJSONSerializer(BaseSerializer):
327
335
 
328
336
  @T.overload
329
337
  @classmethod
330
- def as_desc(cls, metadata: ErrorMetadata) -> ImageDescriptionError: ...
338
+ def as_desc(cls, metadata: ErrorMetadata) -> ErrorDescription: ...
331
339
 
332
340
  @T.overload
333
341
  @classmethod
@@ -336,7 +344,7 @@ class DescriptionJSONSerializer(BaseSerializer):
336
344
  @classmethod
337
345
  def as_desc(cls, metadata):
338
346
  if isinstance(metadata, ErrorMetadata):
339
- return _describe_error_desc(
347
+ return cls._as_error_desc(
340
348
  metadata.error, metadata.filename, metadata.filetype
341
349
  )
342
350
 
@@ -347,6 +355,35 @@ class DescriptionJSONSerializer(BaseSerializer):
347
355
  assert isinstance(metadata, ImageMetadata)
348
356
  return cls._as_image_desc(metadata)
349
357
 
358
+ @classmethod
359
+ def _as_error_desc(
360
+ cls, exc: Exception, filename: Path, filetype: FileType | None
361
+ ) -> ErrorDescription:
362
+ err: _ErrorObject = {
363
+ "type": exc.__class__.__name__,
364
+ "message": str(exc),
365
+ }
366
+
367
+ exc_vars = vars(exc)
368
+
369
+ if exc_vars:
370
+ # handle unserializable exceptions
371
+ try:
372
+ vars_json = json.dumps(exc_vars, sort_keys=True, separators=(",", ":"))
373
+ except Exception:
374
+ vars_json = ""
375
+ if vars_json:
376
+ err["vars"] = json.loads(vars_json)
377
+
378
+ desc: ErrorDescription = {
379
+ "error": err,
380
+ "filename": str(filename.resolve()),
381
+ }
382
+ if filetype is not None:
383
+ desc["filetype"] = filetype.value
384
+
385
+ return desc
386
+
350
387
  @classmethod
351
388
  def _as_video_desc(cls, metadata: VideoMetadata) -> VideoDescription:
352
389
  desc: VideoDescription = {
@@ -354,7 +391,7 @@ class DescriptionJSONSerializer(BaseSerializer):
354
391
  "md5sum": metadata.md5sum,
355
392
  "filetype": metadata.filetype.value,
356
393
  "filesize": metadata.filesize,
357
- "MAPGPSTrack": [cls._encode_point(p) for p in metadata.points],
394
+ "MAPGPSTrack": [PointEncoder.encode(p) for p in metadata.points],
358
395
  }
359
396
  if metadata.make:
360
397
  desc["MAPDeviceMake"] = metadata.make
@@ -442,7 +479,23 @@ class DescriptionJSONSerializer(BaseSerializer):
442
479
  )
443
480
 
444
481
  @classmethod
445
- def _encode_point(cls, p: geo.Point) -> T.Sequence[float | int | None]:
482
+ def _from_video_desc(cls, desc: VideoDescription) -> VideoMetadata:
483
+ validate_video_desc(desc)
484
+
485
+ return VideoMetadata(
486
+ filename=Path(desc["filename"]),
487
+ md5sum=desc.get("md5sum"),
488
+ filesize=desc.get("filesize"),
489
+ filetype=FileType(desc["filetype"]),
490
+ points=[PointEncoder.decode(entry) for entry in desc["MAPGPSTrack"]],
491
+ make=desc.get("MAPDeviceMake"),
492
+ model=desc.get("MAPDeviceModel"),
493
+ )
494
+
495
+
496
+ class PointEncoder:
497
+ @classmethod
498
+ def encode(cls, p: geo.Point) -> T.Sequence[float | int | None]:
446
499
  entry = [
447
500
  int(p.time * 1000),
448
501
  round(p.lon, _COORDINATES_PRECISION),
@@ -453,24 +506,10 @@ class DescriptionJSONSerializer(BaseSerializer):
453
506
  return entry
454
507
 
455
508
  @classmethod
456
- def _decode_point(cls, entry: T.Sequence[T.Any]) -> geo.Point:
509
+ def decode(cls, entry: T.Sequence[T.Any]) -> geo.Point:
457
510
  time_ms, lon, lat, alt, angle = entry
458
511
  return geo.Point(time=time_ms / 1000, lon=lon, lat=lat, alt=alt, angle=angle)
459
512
 
460
- @classmethod
461
- def _from_video_desc(cls, desc: VideoDescription) -> VideoMetadata:
462
- validate_video_desc(desc)
463
-
464
- return VideoMetadata(
465
- filename=Path(desc["filename"]),
466
- md5sum=desc.get("md5sum"),
467
- filesize=desc.get("filesize"),
468
- filetype=FileType(desc["filetype"]),
469
- points=[cls._decode_point(entry) for entry in desc["MAPGPSTrack"]],
470
- make=desc.get("MAPDeviceMake"),
471
- model=desc.get("MAPDeviceModel"),
472
- )
473
-
474
513
 
475
514
  def build_capture_time(time: datetime.datetime | int | float) -> str:
476
515
  if isinstance(time, (float, int)):
@@ -491,7 +530,7 @@ def parse_capture_time(time: str) -> datetime.datetime:
491
530
 
492
531
  def validate_image_desc(desc: T.Any) -> None:
493
532
  try:
494
- jsonschema.validate(instance=desc, schema=ImageDescriptionFileSchema)
533
+ ImageDescriptionFileSchemaValidator.validate(desc)
495
534
  except jsonschema.ValidationError as ex:
496
535
  # do not use str(ex) which is more verbose
497
536
  raise exceptions.MapillaryMetadataValidationError(ex.message) from ex
@@ -504,7 +543,7 @@ def validate_image_desc(desc: T.Any) -> None:
504
543
 
505
544
  def validate_video_desc(desc: T.Any) -> None:
506
545
  try:
507
- jsonschema.validate(instance=desc, schema=VideoDescriptionFileSchema)
546
+ VideoDescriptionFileSchemaValidator.validate(desc)
508
547
  except jsonschema.ValidationError as ex:
509
548
  # do not use str(ex) which is more verbose
510
549
  raise exceptions.MapillaryMetadataValidationError(ex.message) from ex
@@ -554,34 +593,5 @@ def desc_file_to_exif(desc: ImageDescription) -> ImageDescription:
554
593
  return T.cast(ImageDescription, removed)
555
594
 
556
595
 
557
- def _describe_error_desc(
558
- exc: Exception, filename: Path, filetype: FileType | None
559
- ) -> ImageDescriptionError:
560
- err: _ErrorDescription = {
561
- "type": exc.__class__.__name__,
562
- "message": str(exc),
563
- }
564
-
565
- exc_vars = vars(exc)
566
-
567
- if exc_vars:
568
- # handle unserializable exceptions
569
- try:
570
- vars_json = json.dumps(exc_vars)
571
- except Exception:
572
- vars_json = ""
573
- if vars_json:
574
- err["vars"] = json.loads(vars_json)
575
-
576
- desc: ImageDescriptionError = {
577
- "error": err,
578
- "filename": str(filename.resolve()),
579
- }
580
- if filetype is not None:
581
- desc["filetype"] = filetype.value
582
-
583
- return desc
584
-
585
-
586
596
  if __name__ == "__main__":
587
597
  print(json.dumps(ImageVideoDescriptionFileSchema, indent=4))
@@ -129,4 +129,4 @@ class GPXSerializer(BaseSerializer):
129
129
  desc = T.cast(T.Dict, DescriptionJSONSerializer.as_desc(metadata))
130
130
  for prop in excluded_properties:
131
131
  desc.pop(prop, None)
132
- return json.dumps(desc)
132
+ return json.dumps(desc, sort_keys=True, separators=(",", ":"))