mapillary-tools 0.13.3__py3-none-any.whl → 0.14.0__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 (87) hide show
  1. mapillary_tools/__init__.py +1 -1
  2. mapillary_tools/api_v4.py +198 -55
  3. mapillary_tools/authenticate.py +326 -64
  4. mapillary_tools/blackvue_parser.py +195 -0
  5. mapillary_tools/camm/camm_builder.py +55 -97
  6. mapillary_tools/camm/camm_parser.py +429 -181
  7. mapillary_tools/commands/__main__.py +10 -6
  8. mapillary_tools/commands/authenticate.py +8 -1
  9. mapillary_tools/commands/process.py +27 -51
  10. mapillary_tools/commands/process_and_upload.py +18 -5
  11. mapillary_tools/commands/sample_video.py +2 -3
  12. mapillary_tools/commands/upload.py +44 -13
  13. mapillary_tools/commands/video_process_and_upload.py +19 -5
  14. mapillary_tools/config.py +65 -26
  15. mapillary_tools/constants.py +141 -18
  16. mapillary_tools/exceptions.py +37 -34
  17. mapillary_tools/exif_read.py +221 -116
  18. mapillary_tools/exif_write.py +10 -8
  19. mapillary_tools/exiftool_read.py +33 -42
  20. mapillary_tools/exiftool_read_video.py +97 -47
  21. mapillary_tools/exiftool_runner.py +57 -0
  22. mapillary_tools/ffmpeg.py +417 -242
  23. mapillary_tools/geo.py +158 -118
  24. mapillary_tools/geotag/__init__.py +0 -1
  25. mapillary_tools/geotag/base.py +147 -0
  26. mapillary_tools/geotag/factory.py +307 -0
  27. mapillary_tools/geotag/geotag_images_from_exif.py +14 -131
  28. mapillary_tools/geotag/geotag_images_from_exiftool.py +136 -85
  29. mapillary_tools/geotag/geotag_images_from_gpx.py +60 -124
  30. mapillary_tools/geotag/geotag_images_from_gpx_file.py +13 -126
  31. mapillary_tools/geotag/geotag_images_from_nmea_file.py +4 -5
  32. mapillary_tools/geotag/geotag_images_from_video.py +88 -51
  33. mapillary_tools/geotag/geotag_videos_from_exiftool.py +123 -0
  34. mapillary_tools/geotag/geotag_videos_from_gpx.py +52 -0
  35. mapillary_tools/geotag/geotag_videos_from_video.py +20 -185
  36. mapillary_tools/geotag/image_extractors/base.py +18 -0
  37. mapillary_tools/geotag/image_extractors/exif.py +60 -0
  38. mapillary_tools/geotag/image_extractors/exiftool.py +18 -0
  39. mapillary_tools/geotag/options.py +182 -0
  40. mapillary_tools/geotag/utils.py +52 -16
  41. mapillary_tools/geotag/video_extractors/base.py +18 -0
  42. mapillary_tools/geotag/video_extractors/exiftool.py +70 -0
  43. mapillary_tools/geotag/video_extractors/gpx.py +116 -0
  44. mapillary_tools/geotag/video_extractors/native.py +160 -0
  45. mapillary_tools/{geotag → gpmf}/gpmf_parser.py +205 -182
  46. mapillary_tools/{geotag → gpmf}/gps_filter.py +5 -3
  47. mapillary_tools/history.py +134 -20
  48. mapillary_tools/mp4/construct_mp4_parser.py +17 -10
  49. mapillary_tools/mp4/io_utils.py +0 -1
  50. mapillary_tools/mp4/mp4_sample_parser.py +36 -28
  51. mapillary_tools/mp4/simple_mp4_builder.py +10 -9
  52. mapillary_tools/mp4/simple_mp4_parser.py +13 -22
  53. mapillary_tools/process_geotag_properties.py +184 -414
  54. mapillary_tools/process_sequence_properties.py +594 -225
  55. mapillary_tools/sample_video.py +20 -26
  56. mapillary_tools/serializer/description.py +587 -0
  57. mapillary_tools/serializer/gpx.py +132 -0
  58. mapillary_tools/telemetry.py +26 -13
  59. mapillary_tools/types.py +98 -611
  60. mapillary_tools/upload.py +411 -387
  61. mapillary_tools/upload_api_v4.py +167 -142
  62. mapillary_tools/uploader.py +804 -284
  63. mapillary_tools/utils.py +49 -18
  64. {mapillary_tools-0.13.3.dist-info → mapillary_tools-0.14.0.dist-info}/METADATA +93 -35
  65. mapillary_tools-0.14.0.dist-info/RECORD +75 -0
  66. {mapillary_tools-0.13.3.dist-info → mapillary_tools-0.14.0.dist-info}/WHEEL +1 -1
  67. mapillary_tools/geotag/blackvue_parser.py +0 -118
  68. mapillary_tools/geotag/geotag_from_generic.py +0 -22
  69. mapillary_tools/geotag/geotag_images_from_exiftool_both_image_and_video.py +0 -93
  70. mapillary_tools/geotag/geotag_videos_from_exiftool_video.py +0 -145
  71. mapillary_tools/video_data_extraction/cli_options.py +0 -22
  72. mapillary_tools/video_data_extraction/extract_video_data.py +0 -176
  73. mapillary_tools/video_data_extraction/extractors/base_parser.py +0 -75
  74. mapillary_tools/video_data_extraction/extractors/blackvue_parser.py +0 -34
  75. mapillary_tools/video_data_extraction/extractors/camm_parser.py +0 -38
  76. mapillary_tools/video_data_extraction/extractors/exiftool_runtime_parser.py +0 -71
  77. mapillary_tools/video_data_extraction/extractors/exiftool_xml_parser.py +0 -53
  78. mapillary_tools/video_data_extraction/extractors/generic_video_parser.py +0 -52
  79. mapillary_tools/video_data_extraction/extractors/gopro_parser.py +0 -43
  80. mapillary_tools/video_data_extraction/extractors/gpx_parser.py +0 -108
  81. mapillary_tools/video_data_extraction/extractors/nmea_parser.py +0 -24
  82. mapillary_tools/video_data_extraction/video_data_parser_factory.py +0 -39
  83. mapillary_tools-0.13.3.dist-info/RECORD +0 -75
  84. /mapillary_tools/{geotag → gpmf}/gpmf_gps_filter.py +0 -0
  85. {mapillary_tools-0.13.3.dist-info → mapillary_tools-0.14.0.dist-info}/entry_points.txt +0 -0
  86. {mapillary_tools-0.13.3.dist-info → mapillary_tools-0.14.0.dist-info/licenses}/LICENSE +0 -0
  87. {mapillary_tools-0.13.3.dist-info → mapillary_tools-0.14.0.dist-info}/top_level.txt +0 -0
@@ -1,19 +1,90 @@
1
+ from __future__ import annotations
2
+
3
+ import functools
1
4
  import os
2
- import typing as T
5
+ import tempfile
3
6
 
4
7
  import appdirs
5
8
 
6
9
  _ENV_PREFIX = "MAPILLARY_TOOLS_"
7
10
 
8
- ANSI_BOLD = "\033[1m"
9
- ANSI_RESET_ALL = "\033[0m"
10
- CUTOFF_DISTANCE = float(os.getenv(_ENV_PREFIX + "CUTOFF_DISTANCE", 600))
11
- CUTOFF_TIME = float(os.getenv(_ENV_PREFIX + "CUTOFF_TIME", 60))
12
- DUPLICATE_DISTANCE = float(os.getenv(_ENV_PREFIX + "DUPLICATE_DISTANCE", 0.1))
13
- DUPLICATE_ANGLE = float(os.getenv(_ENV_PREFIX + "DUPLICATE_ANGLE", 5))
14
- # in seconds
11
+
12
+ def _yes_or_no(val: str) -> bool:
13
+ return val.strip().upper() in ["1", "TRUE", "YES"]
14
+
15
+
16
+ def _parse_scaled_integers(
17
+ value: str, scale: dict[str, int] | None = None
18
+ ) -> int | None:
19
+ """
20
+ >>> scale = {"": 1, "b": 1, "K": 1024, "M": 1024 * 1024, "G": 1024 * 1024 * 1024}
21
+ >>> _parse_scaled_integers("0", scale=scale)
22
+ 0
23
+ >>> _parse_scaled_integers("10", scale=scale)
24
+ 10
25
+ >>> _parse_scaled_integers("100B", scale=scale)
26
+ 100
27
+ >>> _parse_scaled_integers("100k", scale=scale)
28
+ 102400
29
+ >>> _parse_scaled_integers("100t", scale=scale)
30
+ Traceback (most recent call last):
31
+ ValueError: Expect valid integer ends with , b, K, M, G, but got 100T
32
+ """
33
+
34
+ if scale is None:
35
+ scale = {"": 1}
36
+
37
+ value = value.strip().upper()
38
+
39
+ if value in ["INF", "INFINITY"]:
40
+ return None
41
+
42
+ try:
43
+ for k, v in scale.items():
44
+ k = k.upper()
45
+ if k and value.endswith(k):
46
+ return int(value[: -len(k)]) * v
47
+
48
+ if "" in scale:
49
+ return int(value) * scale[""]
50
+ except ValueError:
51
+ pass
52
+
53
+ raise ValueError(
54
+ f"Expect valid integer ends with {', '.join(scale.keys())}, but got {value}"
55
+ )
56
+
57
+
58
+ _parse_pixels = functools.partial(
59
+ _parse_scaled_integers,
60
+ scale={
61
+ "": 1,
62
+ "K": 1000,
63
+ "M": 1000 * 1000,
64
+ "MP": 1000 * 1000,
65
+ "G": 1000 * 1000 * 1000,
66
+ "GP": 1000 * 1000 * 1000,
67
+ },
68
+ )
69
+
70
+ _parse_filesize = functools.partial(
71
+ _parse_scaled_integers,
72
+ scale={"B": 1, "K": 1024, "M": 1024 * 1024, "G": 1024 * 1024 * 1024},
73
+ )
74
+
75
+ ###################
76
+ ##### GENERAL #####
77
+ ###################
78
+ USER_DATA_DIR = appdirs.user_data_dir(appname="mapillary_tools", appauthor="Mapillary")
79
+ PROMPT_DISABLED: bool = _yes_or_no(os.getenv(_ENV_PREFIX + "PROMPT_DISABLED", "NO"))
80
+
81
+
82
+ ############################
83
+ ##### VIDEO PROCESSING #####
84
+ ############################
85
+ # In seconds
15
86
  VIDEO_SAMPLE_INTERVAL = float(os.getenv(_ENV_PREFIX + "VIDEO_SAMPLE_INTERVAL", -1))
16
- # in meters
87
+ # In meters
17
88
  VIDEO_SAMPLE_DISTANCE = float(os.getenv(_ENV_PREFIX + "VIDEO_SAMPLE_DISTANCE", 3))
18
89
  VIDEO_DURATION_RATIO = float(os.getenv(_ENV_PREFIX + "VIDEO_DURATION_RATIO", 1))
19
90
  FFPROBE_PATH: str = os.getenv(_ENV_PREFIX + "FFPROBE_PATH", "ffprobe")
@@ -25,26 +96,78 @@ IMAGE_DESCRIPTION_FILENAME = os.getenv(
25
96
  SAMPLED_VIDEO_FRAMES_FILENAME = os.getenv(
26
97
  _ENV_PREFIX + "SAMPLED_VIDEO_FRAMES_FILENAME", "mapillary_sampled_video_frames"
27
98
  )
28
- USER_DATA_DIR = appdirs.user_data_dir(appname="mapillary_tools", appauthor="Mapillary")
29
- UPLOAD_CHUNK_SIZE_MB = float(os.getenv(_ENV_PREFIX + "UPLOAD_CHUNK_SIZE_MB", 16))
30
-
31
99
  # DoP value, the lower the better
32
100
  # See https://github.com/gopro/gpmf-parser#hero5-black-with-gps-enabled-adds
33
101
  # It is used to filter out noisy points
34
102
  GOPRO_MAX_DOP100 = int(os.getenv(_ENV_PREFIX + "GOPRO_MAX_DOP100", 1000))
35
103
  # Within the GPS stream: 0 - no lock, 2 or 3 - 2D or 3D Lock
36
- GOPRO_GPS_FIXES: T.Set[int] = set(
104
+ GOPRO_GPS_FIXES: set[int] = set(
37
105
  int(fix) for fix in os.getenv(_ENV_PREFIX + "GOPRO_GPS_FIXES", "2,3").split(",")
38
106
  )
39
- MAX_UPLOAD_RETRIES: int = int(os.getenv(_ENV_PREFIX + "MAX_UPLOAD_RETRIES", 200))
40
-
41
107
  # GPS precision, in meters, is used to filter outliers
42
108
  GOPRO_GPS_PRECISION = float(os.getenv(_ENV_PREFIX + "GOPRO_GPS_PRECISION", 15))
109
+ MAPILLARY__EXPERIMENTAL_ENABLE_IMU: bool = _yes_or_no(
110
+ os.getenv("MAPILLARY__EXPERIMENTAL_ENABLE_IMU", "NO")
111
+ )
112
+
43
113
 
114
+ #################################
115
+ ###### SEQUENCE PROCESSING ######
116
+ #################################
117
+ # In meters
118
+ CUTOFF_DISTANCE = float(os.getenv(_ENV_PREFIX + "CUTOFF_DISTANCE", 600))
119
+ # In seconds
120
+ CUTOFF_TIME = float(os.getenv(_ENV_PREFIX + "CUTOFF_TIME", 60))
121
+ DUPLICATE_DISTANCE = float(os.getenv(_ENV_PREFIX + "DUPLICATE_DISTANCE", 0.1))
122
+ DUPLICATE_ANGLE = float(os.getenv(_ENV_PREFIX + "DUPLICATE_ANGLE", 5))
123
+ MAX_CAPTURE_SPEED_KMH = float(
124
+ os.getenv(_ENV_PREFIX + "MAX_CAPTURE_SPEED_KMH", 400)
125
+ ) # 400 KM/h
44
126
  # WARNING: Changing the following envvars might result in failed uploads
45
127
  # Max number of images per sequence
46
- MAX_SEQUENCE_LENGTH = int(os.getenv(_ENV_PREFIX + "MAX_SEQUENCE_LENGTH", 1000))
128
+ MAX_SEQUENCE_LENGTH: int | None = _parse_scaled_integers(
129
+ os.getenv(_ENV_PREFIX + "MAX_SEQUENCE_LENGTH", "1000")
130
+ )
47
131
  # Max file size per sequence (sum of image filesizes in the sequence)
48
- MAX_SEQUENCE_FILESIZE: str = os.getenv(_ENV_PREFIX + "MAX_SEQUENCE_FILESIZE", "110G")
132
+ MAX_SEQUENCE_FILESIZE: int | None = _parse_filesize(
133
+ os.getenv(_ENV_PREFIX + "MAX_SEQUENCE_FILESIZE", "110G")
134
+ )
49
135
  # Max number of pixels per sequence (sum of image pixels in the sequence)
50
- MAX_SEQUENCE_PIXELS: str = os.getenv(_ENV_PREFIX + "MAX_SEQUENCE_PIXELS", "6G")
136
+ MAX_SEQUENCE_PIXELS: int | None = _parse_pixels(
137
+ os.getenv(_ENV_PREFIX + "MAX_SEQUENCE_PIXELS", "6G")
138
+ )
139
+
140
+
141
+ ##################
142
+ ##### UPLOAD #####
143
+ ##################
144
+ MAPILLARY_DISABLE_API_LOGGING: bool = _yes_or_no(
145
+ os.getenv("MAPILLARY_DISABLE_API_LOGGING", "NO")
146
+ )
147
+ MAPILLARY_UPLOAD_HISTORY_PATH: str = os.getenv(
148
+ "MAPILLARY_UPLOAD_HISTORY_PATH", os.path.join(USER_DATA_DIR, "upload_history")
149
+ )
150
+ UPLOAD_CACHE_DIR: str = os.getenv(
151
+ _ENV_PREFIX + "UPLOAD_CACHE_DIR",
152
+ os.path.join(tempfile.gettempdir(), "mapillary_tools", "upload_cache"),
153
+ )
154
+ # The minimal upload speed is used to calculate the read timeout to avoid upload hanging:
155
+ # timeout = upload_size / MIN_UPLOAD_SPEED
156
+ MIN_UPLOAD_SPEED: int | None = _parse_filesize(
157
+ os.getenv(_ENV_PREFIX + "MIN_UPLOAD_SPEED", "50K") # 50 KiB/s
158
+ )
159
+ MAX_IMAGE_UPLOAD_WORKERS: int = int(
160
+ os.getenv(_ENV_PREFIX + "MAX_IMAGE_UPLOAD_WORKERS", 64)
161
+ )
162
+ # The chunk size in MB (see chunked transfer encoding https://en.wikipedia.org/wiki/Chunked_transfer_encoding)
163
+ # for uploading data to MLY upload service.
164
+ # Changing this size does not change the number of requests nor affect upload performance,
165
+ # but it affects the responsiveness of the upload progress bar
166
+ UPLOAD_CHUNK_SIZE_MB: float = float(os.getenv(_ENV_PREFIX + "UPLOAD_CHUNK_SIZE_MB", 1))
167
+ MAX_UPLOAD_RETRIES: int = int(os.getenv(_ENV_PREFIX + "MAX_UPLOAD_RETRIES", 200))
168
+ MAPILLARY__ENABLE_UPLOAD_HISTORY_FOR_DRY_RUN: bool = _yes_or_no(
169
+ os.getenv("MAPILLARY__ENABLE_UPLOAD_HISTORY_FOR_DRY_RUN", "NO")
170
+ )
171
+ _AUTH_VERIFICATION_DISABLED: bool = _yes_or_no(
172
+ os.getenv(_ENV_PREFIX + "_AUTH_VERIFICATION_DISABLED", "NO")
173
+ )
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import typing as T
2
4
 
3
5
 
@@ -5,6 +7,18 @@ class MapillaryUserError(Exception):
5
7
  exit_code: int
6
8
 
7
9
 
10
+ class MapillaryProcessError(MapillaryUserError):
11
+ """
12
+ Base exception for process specific errors
13
+ """
14
+
15
+ exit_code = 6
16
+
17
+
18
+ class MapillaryDescriptionError(Exception):
19
+ pass
20
+
21
+
8
22
  class MapillaryBadParameterError(MapillaryUserError):
9
23
  exit_code = 2
10
24
 
@@ -17,47 +31,42 @@ class MapillaryInvalidDescriptionFile(MapillaryUserError):
17
31
  exit_code = 4
18
32
 
19
33
 
20
- class MapillaryUnknownFileTypeError(MapillaryUserError):
21
- exit_code = 5
22
-
23
-
24
- class MapillaryProcessError(MapillaryUserError):
25
- exit_code = 6
26
-
27
-
28
34
  class MapillaryVideoError(MapillaryUserError):
29
35
  exit_code = 7
30
36
 
31
37
 
32
38
  class MapillaryFFmpegNotFoundError(MapillaryUserError):
33
39
  exit_code = 8
34
- help = "https://github.com/mapillary/mapillary_tools#video-support"
35
40
 
36
41
 
37
42
  class MapillaryExiftoolNotFoundError(MapillaryUserError):
38
43
  exit_code = 8
39
44
 
40
45
 
41
- class MapillaryDescriptionError(Exception):
46
+ class MapillaryGeoTaggingError(MapillaryDescriptionError):
42
47
  pass
43
48
 
44
49
 
45
- class MapillaryGeoTaggingError(MapillaryDescriptionError):
50
+ class MapillaryVideoGPSNotFoundError(MapillaryDescriptionError):
46
51
  pass
47
52
 
48
53
 
49
- class MapillaryGPXEmptyError(MapillaryDescriptionError, MapillaryUserError):
50
- exit_code = 9
54
+ class MapillaryInvalidVideoError(MapillaryDescriptionError):
55
+ pass
51
56
 
52
57
 
53
- class MapillaryVideoGPSNotFoundError(MapillaryDescriptionError, MapillaryUserError):
54
- exit_code = 9
58
+ class MapillaryGPXEmptyError(MapillaryDescriptionError):
59
+ pass
55
60
 
56
61
 
57
62
  class MapillaryGPSNoiseError(MapillaryDescriptionError):
58
63
  pass
59
64
 
60
65
 
66
+ class MapillaryStationaryVideoError(MapillaryDescriptionError):
67
+ pass
68
+
69
+
61
70
  class MapillaryOutsideGPXTrackError(MapillaryDescriptionError):
62
71
  def __init__(
63
72
  self, message: str, image_time: str, gpx_start_time: str, gpx_end_time: str
@@ -68,21 +77,13 @@ class MapillaryOutsideGPXTrackError(MapillaryDescriptionError):
68
77
  self.gpx_end_time = gpx_end_time
69
78
 
70
79
 
71
- class MapillaryStationaryVideoError(MapillaryDescriptionError, MapillaryUserError):
72
- exit_code = 10
73
-
74
-
75
- class MapillaryInvalidBlackVueVideoError(MapillaryDescriptionError, MapillaryUserError):
76
- exit_code = 11
77
-
78
-
79
80
  class MapillaryDuplicationError(MapillaryDescriptionError):
80
81
  def __init__(
81
82
  self,
82
83
  message: str,
83
84
  desc: T.Mapping[str, T.Any],
84
85
  distance: float,
85
- angle_diff: T.Optional[float],
86
+ angle_diff: float | None,
86
87
  ) -> None:
87
88
  super().__init__(message)
88
89
  self.desc = desc
@@ -90,17 +91,19 @@ class MapillaryDuplicationError(MapillaryDescriptionError):
90
91
  self.angle_diff = angle_diff
91
92
 
92
93
 
93
- class MapillaryUploadedAlreadyError(MapillaryDescriptionError):
94
- def __init__(
95
- self,
96
- message: str,
97
- desc: T.Mapping[str, T.Any],
98
- ) -> None:
99
- super().__init__(message)
100
- self.desc = desc
94
+ class MapillaryExifToolXMLNotFoundError(MapillaryDescriptionError):
95
+ pass
96
+
97
+
98
+ class MapillaryFileTooLargeError(MapillaryDescriptionError):
99
+ pass
100
+
101
+
102
+ class MapillaryCaptureSpeedTooFastError(MapillaryDescriptionError):
103
+ pass
101
104
 
102
105
 
103
- class MapillaryEXIFNotFoundError(MapillaryDescriptionError):
106
+ class MapillaryNullIslandError(MapillaryDescriptionError):
104
107
  pass
105
108
 
106
109
 
@@ -116,5 +119,5 @@ class MapillaryUploadUnauthorizedError(MapillaryUserError):
116
119
  exit_code = 14
117
120
 
118
121
 
119
- class MapillaryMetadataValidationError(MapillaryUserError, MapillaryDescriptionError):
122
+ class MapillaryMetadataValidationError(MapillaryUserError):
120
123
  exit_code = 15