mapillary-tools 0.14.0b1__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 (36) hide show
  1. mapillary_tools/__init__.py +1 -1
  2. mapillary_tools/api_v4.py +66 -263
  3. mapillary_tools/authenticate.py +46 -38
  4. mapillary_tools/commands/__main__.py +15 -16
  5. mapillary_tools/commands/upload.py +33 -4
  6. mapillary_tools/constants.py +127 -45
  7. mapillary_tools/exceptions.py +4 -0
  8. mapillary_tools/exif_read.py +2 -1
  9. mapillary_tools/exif_write.py +3 -1
  10. mapillary_tools/geo.py +16 -0
  11. mapillary_tools/geotag/base.py +6 -2
  12. mapillary_tools/geotag/factory.py +9 -1
  13. mapillary_tools/geotag/geotag_images_from_exiftool.py +1 -1
  14. mapillary_tools/geotag/geotag_images_from_gpx.py +0 -6
  15. mapillary_tools/geotag/geotag_videos_from_exiftool.py +30 -9
  16. mapillary_tools/geotag/utils.py +9 -12
  17. mapillary_tools/geotag/video_extractors/gpx.py +2 -1
  18. mapillary_tools/geotag/video_extractors/native.py +25 -0
  19. mapillary_tools/history.py +124 -7
  20. mapillary_tools/http.py +211 -0
  21. mapillary_tools/mp4/construct_mp4_parser.py +8 -2
  22. mapillary_tools/process_geotag_properties.py +31 -27
  23. mapillary_tools/process_sequence_properties.py +339 -322
  24. mapillary_tools/sample_video.py +1 -2
  25. mapillary_tools/serializer/description.py +56 -56
  26. mapillary_tools/serializer/gpx.py +1 -1
  27. mapillary_tools/upload.py +201 -205
  28. mapillary_tools/upload_api_v4.py +57 -47
  29. mapillary_tools/uploader.py +720 -285
  30. mapillary_tools/utils.py +57 -5
  31. {mapillary_tools-0.14.0b1.dist-info → mapillary_tools-0.14.1.dist-info}/METADATA +7 -6
  32. {mapillary_tools-0.14.0b1.dist-info → mapillary_tools-0.14.1.dist-info}/RECORD +36 -35
  33. {mapillary_tools-0.14.0b1.dist-info → mapillary_tools-0.14.1.dist-info}/WHEEL +0 -0
  34. {mapillary_tools-0.14.0b1.dist-info → mapillary_tools-0.14.1.dist-info}/entry_points.txt +0 -0
  35. {mapillary_tools-0.14.0b1.dist-info → mapillary_tools-0.14.1.dist-info}/licenses/LICENSE +0 -0
  36. {mapillary_tools-0.14.0b1.dist-info → mapillary_tools-0.14.1.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 = {
@@ -307,7 +305,7 @@ class DescriptionJSONSerializer(BaseSerializer):
307
305
  @classmethod
308
306
  def serialize(cls, metadatas: T.Sequence[MetadataOrError]) -> bytes:
309
307
  descs = [cls.as_desc(m) for m in metadatas]
310
- return json.dumps(descs).encode("utf-8")
308
+ return json.dumps(descs, sort_keys=True, separators=(",", ":")).encode("utf-8")
311
309
 
312
310
  @override
313
311
  @classmethod
@@ -327,7 +325,7 @@ class DescriptionJSONSerializer(BaseSerializer):
327
325
 
328
326
  @T.overload
329
327
  @classmethod
330
- def as_desc(cls, metadata: ErrorMetadata) -> ImageDescriptionError: ...
328
+ def as_desc(cls, metadata: ErrorMetadata) -> ErrorDescription: ...
331
329
 
332
330
  @T.overload
333
331
  @classmethod
@@ -336,7 +334,7 @@ class DescriptionJSONSerializer(BaseSerializer):
336
334
  @classmethod
337
335
  def as_desc(cls, metadata):
338
336
  if isinstance(metadata, ErrorMetadata):
339
- return _describe_error_desc(
337
+ return cls._as_error_desc(
340
338
  metadata.error, metadata.filename, metadata.filetype
341
339
  )
342
340
 
@@ -347,6 +345,35 @@ class DescriptionJSONSerializer(BaseSerializer):
347
345
  assert isinstance(metadata, ImageMetadata)
348
346
  return cls._as_image_desc(metadata)
349
347
 
348
+ @classmethod
349
+ def _as_error_desc(
350
+ cls, exc: Exception, filename: Path, filetype: FileType | None
351
+ ) -> ErrorDescription:
352
+ err: _ErrorObject = {
353
+ "type": exc.__class__.__name__,
354
+ "message": str(exc),
355
+ }
356
+
357
+ exc_vars = vars(exc)
358
+
359
+ if exc_vars:
360
+ # handle unserializable exceptions
361
+ try:
362
+ vars_json = json.dumps(exc_vars, sort_keys=True, separators=(",", ":"))
363
+ except Exception:
364
+ vars_json = ""
365
+ if vars_json:
366
+ err["vars"] = json.loads(vars_json)
367
+
368
+ desc: ErrorDescription = {
369
+ "error": err,
370
+ "filename": str(filename.resolve()),
371
+ }
372
+ if filetype is not None:
373
+ desc["filetype"] = filetype.value
374
+
375
+ return desc
376
+
350
377
  @classmethod
351
378
  def _as_video_desc(cls, metadata: VideoMetadata) -> VideoDescription:
352
379
  desc: VideoDescription = {
@@ -354,7 +381,7 @@ class DescriptionJSONSerializer(BaseSerializer):
354
381
  "md5sum": metadata.md5sum,
355
382
  "filetype": metadata.filetype.value,
356
383
  "filesize": metadata.filesize,
357
- "MAPGPSTrack": [cls._encode_point(p) for p in metadata.points],
384
+ "MAPGPSTrack": [PointEncoder.encode(p) for p in metadata.points],
358
385
  }
359
386
  if metadata.make:
360
387
  desc["MAPDeviceMake"] = metadata.make
@@ -442,7 +469,23 @@ class DescriptionJSONSerializer(BaseSerializer):
442
469
  )
443
470
 
444
471
  @classmethod
445
- def _encode_point(cls, p: geo.Point) -> T.Sequence[float | int | None]:
472
+ def _from_video_desc(cls, desc: VideoDescription) -> VideoMetadata:
473
+ validate_video_desc(desc)
474
+
475
+ return VideoMetadata(
476
+ filename=Path(desc["filename"]),
477
+ md5sum=desc.get("md5sum"),
478
+ filesize=desc.get("filesize"),
479
+ filetype=FileType(desc["filetype"]),
480
+ points=[PointEncoder.decode(entry) for entry in desc["MAPGPSTrack"]],
481
+ make=desc.get("MAPDeviceMake"),
482
+ model=desc.get("MAPDeviceModel"),
483
+ )
484
+
485
+
486
+ class PointEncoder:
487
+ @classmethod
488
+ def encode(cls, p: geo.Point) -> T.Sequence[float | int | None]:
446
489
  entry = [
447
490
  int(p.time * 1000),
448
491
  round(p.lon, _COORDINATES_PRECISION),
@@ -453,24 +496,10 @@ class DescriptionJSONSerializer(BaseSerializer):
453
496
  return entry
454
497
 
455
498
  @classmethod
456
- def _decode_point(cls, entry: T.Sequence[T.Any]) -> geo.Point:
499
+ def decode(cls, entry: T.Sequence[T.Any]) -> geo.Point:
457
500
  time_ms, lon, lat, alt, angle = entry
458
501
  return geo.Point(time=time_ms / 1000, lon=lon, lat=lat, alt=alt, angle=angle)
459
502
 
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
503
 
475
504
  def build_capture_time(time: datetime.datetime | int | float) -> str:
476
505
  if isinstance(time, (float, int)):
@@ -554,34 +583,5 @@ def desc_file_to_exif(desc: ImageDescription) -> ImageDescription:
554
583
  return T.cast(ImageDescription, removed)
555
584
 
556
585
 
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
586
  if __name__ == "__main__":
587
587
  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=(",", ":"))