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.
- mapillary_tools/__init__.py +1 -1
- mapillary_tools/api_v4.py +66 -263
- mapillary_tools/authenticate.py +47 -39
- mapillary_tools/commands/__main__.py +15 -16
- mapillary_tools/commands/upload.py +33 -4
- mapillary_tools/config.py +5 -0
- 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/options.py +4 -1
- 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 +35 -38
- mapillary_tools/process_sequence_properties.py +339 -322
- mapillary_tools/sample_video.py +1 -2
- mapillary_tools/serializer/description.py +68 -58
- mapillary_tools/serializer/gpx.py +1 -1
- mapillary_tools/upload.py +202 -207
- mapillary_tools/upload_api_v4.py +57 -47
- mapillary_tools/uploader.py +728 -285
- mapillary_tools/utils.py +57 -5
- {mapillary_tools-0.14.0b1.dist-info → mapillary_tools-0.14.2.dist-info}/METADATA +7 -6
- {mapillary_tools-0.14.0b1.dist-info → mapillary_tools-0.14.2.dist-info}/RECORD +38 -37
- {mapillary_tools-0.14.0b1.dist-info → mapillary_tools-0.14.2.dist-info}/WHEEL +0 -0
- {mapillary_tools-0.14.0b1.dist-info → mapillary_tools-0.14.2.dist-info}/entry_points.txt +0 -0
- {mapillary_tools-0.14.0b1.dist-info → mapillary_tools-0.14.2.dist-info}/licenses/LICENSE +0 -0
- {mapillary_tools-0.14.0b1.dist-info → mapillary_tools-0.14.2.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 = {
|
|
@@ -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) ->
|
|
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
|
|
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": [
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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=(",", ":"))
|