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.
- mapillary_tools/__init__.py +1 -1
- mapillary_tools/api_v4.py +66 -263
- mapillary_tools/authenticate.py +46 -38
- mapillary_tools/commands/__main__.py +15 -16
- mapillary_tools/commands/upload.py +33 -4
- mapillary_tools/constants.py +127 -45
- mapillary_tools/exceptions.py +4 -0
- mapillary_tools/exif_read.py +2 -1
- mapillary_tools/exif_write.py +3 -1
- mapillary_tools/geo.py +16 -0
- mapillary_tools/geotag/base.py +6 -2
- mapillary_tools/geotag/factory.py +9 -1
- mapillary_tools/geotag/geotag_images_from_exiftool.py +1 -1
- mapillary_tools/geotag/geotag_images_from_gpx.py +0 -6
- mapillary_tools/geotag/geotag_videos_from_exiftool.py +30 -9
- mapillary_tools/geotag/utils.py +9 -12
- mapillary_tools/geotag/video_extractors/gpx.py +2 -1
- mapillary_tools/geotag/video_extractors/native.py +25 -0
- mapillary_tools/history.py +124 -7
- mapillary_tools/http.py +211 -0
- mapillary_tools/mp4/construct_mp4_parser.py +8 -2
- mapillary_tools/process_geotag_properties.py +31 -27
- mapillary_tools/process_sequence_properties.py +339 -322
- mapillary_tools/sample_video.py +1 -2
- mapillary_tools/serializer/description.py +56 -56
- mapillary_tools/serializer/gpx.py +1 -1
- mapillary_tools/upload.py +201 -205
- mapillary_tools/upload_api_v4.py +57 -47
- mapillary_tools/uploader.py +720 -285
- mapillary_tools/utils.py +57 -5
- {mapillary_tools-0.14.0b1.dist-info → mapillary_tools-0.14.1.dist-info}/METADATA +7 -6
- {mapillary_tools-0.14.0b1.dist-info → mapillary_tools-0.14.1.dist-info}/RECORD +36 -35
- {mapillary_tools-0.14.0b1.dist-info → mapillary_tools-0.14.1.dist-info}/WHEEL +0 -0
- {mapillary_tools-0.14.0b1.dist-info → mapillary_tools-0.14.1.dist-info}/entry_points.txt +0 -0
- {mapillary_tools-0.14.0b1.dist-info → mapillary_tools-0.14.1.dist-info}/licenses/LICENSE +0 -0
- {mapillary_tools-0.14.0b1.dist-info → mapillary_tools-0.14.1.dist-info}/top_level.txt +0 -0
mapillary_tools/sample_video.py
CHANGED
|
@@ -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=
|
|
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
|
|
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
|
|
95
|
+
class ErrorDescription(TypedDict, total=False):
|
|
98
96
|
filename: Required[str]
|
|
99
|
-
error: Required[
|
|
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,
|
|
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) ->
|
|
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
|
|
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": [
|
|
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
|
|
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
|
|
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=(",", ":"))
|