mapillary-tools 0.13.3a1__py3-none-any.whl → 0.14.0a1__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 +235 -14
- mapillary_tools/authenticate.py +325 -64
- mapillary_tools/{geotag/blackvue_parser.py → blackvue_parser.py} +74 -54
- mapillary_tools/camm/camm_builder.py +55 -97
- mapillary_tools/camm/camm_parser.py +425 -177
- mapillary_tools/commands/__main__.py +11 -4
- mapillary_tools/commands/authenticate.py +8 -1
- mapillary_tools/commands/process.py +27 -51
- mapillary_tools/commands/process_and_upload.py +19 -5
- mapillary_tools/commands/sample_video.py +2 -3
- mapillary_tools/commands/upload.py +18 -9
- mapillary_tools/commands/video_process_and_upload.py +19 -5
- mapillary_tools/config.py +28 -12
- mapillary_tools/constants.py +46 -4
- mapillary_tools/exceptions.py +34 -35
- mapillary_tools/exif_read.py +158 -53
- mapillary_tools/exiftool_read.py +19 -5
- mapillary_tools/exiftool_read_video.py +12 -1
- mapillary_tools/exiftool_runner.py +77 -0
- mapillary_tools/geo.py +148 -107
- mapillary_tools/geotag/factory.py +298 -0
- mapillary_tools/geotag/geotag_from_generic.py +152 -11
- mapillary_tools/geotag/geotag_images_from_exif.py +43 -124
- mapillary_tools/geotag/geotag_images_from_exiftool.py +66 -70
- mapillary_tools/geotag/geotag_images_from_exiftool_both_image_and_video.py +32 -48
- mapillary_tools/geotag/geotag_images_from_gpx.py +41 -116
- mapillary_tools/geotag/geotag_images_from_gpx_file.py +15 -96
- mapillary_tools/geotag/geotag_images_from_nmea_file.py +4 -2
- mapillary_tools/geotag/geotag_images_from_video.py +46 -46
- mapillary_tools/geotag/geotag_videos_from_exiftool_video.py +98 -92
- mapillary_tools/geotag/geotag_videos_from_gpx.py +140 -0
- mapillary_tools/geotag/geotag_videos_from_video.py +149 -181
- mapillary_tools/geotag/options.py +159 -0
- mapillary_tools/{geotag → gpmf}/gpmf_parser.py +194 -171
- mapillary_tools/history.py +3 -11
- mapillary_tools/mp4/io_utils.py +0 -1
- mapillary_tools/mp4/mp4_sample_parser.py +11 -3
- mapillary_tools/mp4/simple_mp4_parser.py +0 -10
- mapillary_tools/process_geotag_properties.py +151 -386
- mapillary_tools/process_sequence_properties.py +554 -202
- mapillary_tools/sample_video.py +8 -15
- mapillary_tools/telemetry.py +24 -12
- mapillary_tools/types.py +80 -22
- mapillary_tools/upload.py +316 -298
- mapillary_tools/upload_api_v4.py +55 -122
- mapillary_tools/uploader.py +396 -254
- mapillary_tools/utils.py +26 -0
- mapillary_tools/video_data_extraction/extract_video_data.py +17 -36
- mapillary_tools/video_data_extraction/extractors/blackvue_parser.py +34 -19
- mapillary_tools/video_data_extraction/extractors/camm_parser.py +41 -17
- mapillary_tools/video_data_extraction/extractors/exiftool_runtime_parser.py +4 -1
- mapillary_tools/video_data_extraction/extractors/exiftool_xml_parser.py +1 -2
- mapillary_tools/video_data_extraction/extractors/gopro_parser.py +37 -22
- {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0a1.dist-info}/METADATA +3 -2
- mapillary_tools-0.14.0a1.dist-info/RECORD +78 -0
- {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0a1.dist-info}/WHEEL +1 -1
- mapillary_tools/geotag/utils.py +0 -26
- mapillary_tools-0.13.3a1.dist-info/RECORD +0 -75
- /mapillary_tools/{geotag → gpmf}/gpmf_gps_filter.py +0 -0
- /mapillary_tools/{geotag → gpmf}/gps_filter.py +0 -0
- {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0a1.dist-info}/entry_points.txt +0 -0
- {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0a1.dist-info/licenses}/LICENSE +0 -0
- {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0a1.dist-info}/top_level.txt +0 -0
mapillary_tools/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
VERSION = "0.
|
|
1
|
+
VERSION = "0.14.0a1"
|
mapillary_tools/api_v4.py
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import enum
|
|
1
4
|
import logging
|
|
2
5
|
import os
|
|
3
6
|
import ssl
|
|
4
7
|
import typing as T
|
|
8
|
+
from json import dumps
|
|
5
9
|
|
|
6
10
|
import requests
|
|
7
11
|
from requests.adapters import HTTPAdapter
|
|
@@ -17,6 +21,12 @@ REQUESTS_TIMEOUT = 60 # 1 minutes
|
|
|
17
21
|
USE_SYSTEM_CERTS: bool = False
|
|
18
22
|
|
|
19
23
|
|
|
24
|
+
class ClusterFileType(enum.Enum):
|
|
25
|
+
ZIP = "zip"
|
|
26
|
+
BLACKVUE = "mly_blackvue_video"
|
|
27
|
+
CAMM = "mly_camm_video"
|
|
28
|
+
|
|
29
|
+
|
|
20
30
|
class HTTPSystemCertsAdapter(HTTPAdapter):
|
|
21
31
|
"""
|
|
22
32
|
This adapter uses the system's certificate store instead of the certifi module.
|
|
@@ -46,22 +56,132 @@ class HTTPSystemCertsAdapter(HTTPAdapter):
|
|
|
46
56
|
conn.ca_certs = None
|
|
47
57
|
|
|
48
58
|
|
|
59
|
+
@T.overload
|
|
60
|
+
def _truncate(s: bytes, limit: int = 512) -> bytes: ...
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@T.overload
|
|
64
|
+
def _truncate(s: str, limit: int = 512) -> str: ...
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _truncate(s, limit=512):
|
|
68
|
+
if limit < len(s):
|
|
69
|
+
remaining = len(s) - limit
|
|
70
|
+
if isinstance(s, bytes):
|
|
71
|
+
return (
|
|
72
|
+
s[:limit]
|
|
73
|
+
+ b"..."
|
|
74
|
+
+ f"({remaining} more bytes truncated)".encode("utf-8")
|
|
75
|
+
)
|
|
76
|
+
else:
|
|
77
|
+
return str(s[:limit]) + f"...({remaining} more chars truncated)"
|
|
78
|
+
else:
|
|
79
|
+
return s
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _sanitize(headers: T.Mapping[T.Any, T.Any]) -> T.Mapping[T.Any, T.Any]:
|
|
83
|
+
new_headers = {}
|
|
84
|
+
|
|
85
|
+
for k, v in headers.items():
|
|
86
|
+
if k.lower() in [
|
|
87
|
+
"authorization",
|
|
88
|
+
"cookie",
|
|
89
|
+
"x-fb-access-token",
|
|
90
|
+
"access-token",
|
|
91
|
+
"access_token",
|
|
92
|
+
"password",
|
|
93
|
+
"user_upload_token",
|
|
94
|
+
]:
|
|
95
|
+
new_headers[k] = "[REDACTED]"
|
|
96
|
+
else:
|
|
97
|
+
new_headers[k] = _truncate(v)
|
|
98
|
+
|
|
99
|
+
return new_headers
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _log_debug_request(
|
|
103
|
+
method: str,
|
|
104
|
+
url: str,
|
|
105
|
+
json: dict | None = None,
|
|
106
|
+
params: dict | None = None,
|
|
107
|
+
headers: dict | None = None,
|
|
108
|
+
timeout: T.Any = None,
|
|
109
|
+
):
|
|
110
|
+
if logging.getLogger().getEffectiveLevel() <= logging.DEBUG:
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
msg = f"HTTP {method} {url}"
|
|
114
|
+
|
|
115
|
+
if USE_SYSTEM_CERTS:
|
|
116
|
+
msg += " (w/sys_certs)"
|
|
117
|
+
|
|
118
|
+
if json:
|
|
119
|
+
t = _truncate(dumps(_sanitize(json)))
|
|
120
|
+
msg += f" JSON={t}"
|
|
121
|
+
|
|
122
|
+
if params:
|
|
123
|
+
msg += f" PARAMS={_sanitize(params)}"
|
|
124
|
+
|
|
125
|
+
if headers:
|
|
126
|
+
msg += f" HEADERS={_sanitize(headers)}"
|
|
127
|
+
|
|
128
|
+
if timeout is not None:
|
|
129
|
+
msg += f" TIMEOUT={timeout}"
|
|
130
|
+
|
|
131
|
+
LOG.debug(msg)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _log_debug_response(resp: requests.Response):
|
|
135
|
+
if logging.getLogger().getEffectiveLevel() <= logging.DEBUG:
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
data: T.Union[str, bytes]
|
|
139
|
+
try:
|
|
140
|
+
data = _truncate(dumps(_sanitize(resp.json())))
|
|
141
|
+
except Exception:
|
|
142
|
+
data = _truncate(resp.content)
|
|
143
|
+
|
|
144
|
+
LOG.debug(f"HTTP {resp.status_code} ({resp.reason}): %s", data)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def readable_http_error(ex: requests.HTTPError) -> str:
|
|
148
|
+
req = ex.request
|
|
149
|
+
resp = ex.response
|
|
150
|
+
|
|
151
|
+
data: T.Union[str, bytes]
|
|
152
|
+
try:
|
|
153
|
+
data = _truncate(dumps(_sanitize(resp.json())))
|
|
154
|
+
except Exception:
|
|
155
|
+
data = _truncate(resp.content)
|
|
156
|
+
|
|
157
|
+
return f"{req.method} {resp.url} => {resp.status_code} ({resp.reason}): {str(data)}"
|
|
158
|
+
|
|
159
|
+
|
|
49
160
|
def request_post(
|
|
50
161
|
url: str,
|
|
51
|
-
data: T.
|
|
52
|
-
json:
|
|
162
|
+
data: T.Any | None = None,
|
|
163
|
+
json: dict | None = None,
|
|
53
164
|
**kwargs,
|
|
54
165
|
) -> requests.Response:
|
|
55
166
|
global USE_SYSTEM_CERTS
|
|
56
167
|
|
|
168
|
+
_log_debug_request(
|
|
169
|
+
"POST",
|
|
170
|
+
url,
|
|
171
|
+
json=json,
|
|
172
|
+
params=kwargs.get("params"),
|
|
173
|
+
headers=kwargs.get("headers"),
|
|
174
|
+
timeout=kwargs.get("timeout"),
|
|
175
|
+
)
|
|
176
|
+
|
|
57
177
|
if USE_SYSTEM_CERTS:
|
|
58
178
|
with requests.Session() as session:
|
|
59
179
|
session.mount("https://", HTTPSystemCertsAdapter())
|
|
60
|
-
|
|
180
|
+
resp = session.post(url, data=data, json=json, **kwargs)
|
|
61
181
|
|
|
62
182
|
else:
|
|
63
183
|
try:
|
|
64
|
-
|
|
184
|
+
resp = requests.post(url, data=data, json=json, **kwargs)
|
|
65
185
|
except requests.exceptions.SSLError as ex:
|
|
66
186
|
if "SSLCertVerificationError" not in str(ex):
|
|
67
187
|
raise ex
|
|
@@ -70,25 +190,35 @@ def request_post(
|
|
|
70
190
|
LOG.warning(
|
|
71
191
|
"SSL error occurred, falling back to system SSL certificates: %s", ex
|
|
72
192
|
)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
193
|
+
return request_post(url, data=data, json=json, **kwargs)
|
|
194
|
+
|
|
195
|
+
_log_debug_response(resp)
|
|
196
|
+
|
|
197
|
+
return resp
|
|
76
198
|
|
|
77
199
|
|
|
78
200
|
def request_get(
|
|
79
201
|
url: str,
|
|
80
|
-
params:
|
|
202
|
+
params: dict | None = None,
|
|
81
203
|
**kwargs,
|
|
82
204
|
) -> requests.Response:
|
|
83
205
|
global USE_SYSTEM_CERTS
|
|
84
206
|
|
|
207
|
+
_log_debug_request(
|
|
208
|
+
"GET",
|
|
209
|
+
url,
|
|
210
|
+
params=kwargs.get("params"),
|
|
211
|
+
headers=kwargs.get("headers"),
|
|
212
|
+
timeout=kwargs.get("timeout"),
|
|
213
|
+
)
|
|
214
|
+
|
|
85
215
|
if USE_SYSTEM_CERTS:
|
|
86
216
|
with requests.Session() as session:
|
|
87
217
|
session.mount("https://", HTTPSystemCertsAdapter())
|
|
88
|
-
|
|
218
|
+
resp = session.get(url, params=params, **kwargs)
|
|
89
219
|
else:
|
|
90
220
|
try:
|
|
91
|
-
|
|
221
|
+
resp = requests.get(url, params=params, **kwargs)
|
|
92
222
|
except requests.exceptions.SSLError as ex:
|
|
93
223
|
if "SSLCertVerificationError" not in str(ex):
|
|
94
224
|
raise ex
|
|
@@ -97,15 +227,55 @@ def request_get(
|
|
|
97
227
|
LOG.warning(
|
|
98
228
|
"SSL error occurred, falling back to system SSL certificates: %s", ex
|
|
99
229
|
)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
230
|
+
resp = request_get(url, params=params, **kwargs)
|
|
231
|
+
|
|
232
|
+
_log_debug_response(resp)
|
|
233
|
+
|
|
234
|
+
return resp
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def is_auth_error(resp: requests.Response) -> bool:
|
|
238
|
+
if resp.status_code in [401, 403]:
|
|
239
|
+
return True
|
|
240
|
+
|
|
241
|
+
if resp.status_code in [400]:
|
|
242
|
+
try:
|
|
243
|
+
error_body = resp.json()
|
|
244
|
+
except Exception:
|
|
245
|
+
error_body = {}
|
|
246
|
+
|
|
247
|
+
type = error_body.get("debug_info", {}).get("type")
|
|
248
|
+
if type in ["NotAuthorizedError"]:
|
|
249
|
+
return True
|
|
250
|
+
|
|
251
|
+
return False
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def extract_auth_error_message(resp: requests.Response) -> str:
|
|
255
|
+
assert is_auth_error(resp), "has to be an auth error"
|
|
256
|
+
|
|
257
|
+
try:
|
|
258
|
+
error_body = resp.json()
|
|
259
|
+
except Exception:
|
|
260
|
+
error_body = {}
|
|
261
|
+
|
|
262
|
+
# from Graph APIs
|
|
263
|
+
message = error_body.get("error", {}).get("message")
|
|
264
|
+
if message is not None:
|
|
265
|
+
return str(message)
|
|
266
|
+
|
|
267
|
+
# from upload service
|
|
268
|
+
message = error_body.get("debug_info", {}).get("message")
|
|
269
|
+
if message is not None:
|
|
270
|
+
return str(message)
|
|
271
|
+
|
|
272
|
+
return resp.text
|
|
103
273
|
|
|
104
274
|
|
|
105
275
|
def get_upload_token(email: str, password: str) -> requests.Response:
|
|
106
276
|
resp = request_post(
|
|
107
277
|
f"{MAPILLARY_GRAPH_API_ENDPOINT}/login",
|
|
108
|
-
|
|
278
|
+
headers={"Authorization": f"OAuth {MAPILLARY_CLIENT_TOKEN}"},
|
|
109
279
|
json={"email": email, "password": password, "locale": "en_US"},
|
|
110
280
|
timeout=REQUESTS_TIMEOUT,
|
|
111
281
|
)
|
|
@@ -130,6 +300,30 @@ def fetch_organization(
|
|
|
130
300
|
return resp
|
|
131
301
|
|
|
132
302
|
|
|
303
|
+
def fetch_user_or_me(
|
|
304
|
+
user_access_token: str,
|
|
305
|
+
user_id: int | str | None = None,
|
|
306
|
+
) -> requests.Response:
|
|
307
|
+
if user_id is None:
|
|
308
|
+
url = f"{MAPILLARY_GRAPH_API_ENDPOINT}/me"
|
|
309
|
+
else:
|
|
310
|
+
url = f"{MAPILLARY_GRAPH_API_ENDPOINT}/{user_id}"
|
|
311
|
+
|
|
312
|
+
resp = request_get(
|
|
313
|
+
url,
|
|
314
|
+
params={
|
|
315
|
+
"fields": ",".join(["id", "username"]),
|
|
316
|
+
},
|
|
317
|
+
headers={
|
|
318
|
+
"Authorization": f"OAuth {user_access_token}",
|
|
319
|
+
},
|
|
320
|
+
timeout=REQUESTS_TIMEOUT,
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
resp.raise_for_status()
|
|
324
|
+
return resp
|
|
325
|
+
|
|
326
|
+
|
|
133
327
|
ActionType = T.Literal[
|
|
134
328
|
"upload_started_upload", "upload_finished_upload", "upload_failed_upload"
|
|
135
329
|
]
|
|
@@ -149,3 +343,30 @@ def log_event(action_type: ActionType, properties: T.Dict) -> requests.Response:
|
|
|
149
343
|
)
|
|
150
344
|
resp.raise_for_status()
|
|
151
345
|
return resp
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def finish_upload(
|
|
349
|
+
user_access_token: str,
|
|
350
|
+
file_handle: str,
|
|
351
|
+
cluster_filetype: ClusterFileType,
|
|
352
|
+
organization_id: int | str | None = None,
|
|
353
|
+
) -> requests.Response:
|
|
354
|
+
data: dict[str, str | int] = {
|
|
355
|
+
"file_handle": file_handle,
|
|
356
|
+
"file_type": cluster_filetype.value,
|
|
357
|
+
}
|
|
358
|
+
if organization_id is not None:
|
|
359
|
+
data["organization_id"] = organization_id
|
|
360
|
+
|
|
361
|
+
resp = request_post(
|
|
362
|
+
f"{MAPILLARY_GRAPH_API_ENDPOINT}/finish_upload",
|
|
363
|
+
headers={
|
|
364
|
+
"Authorization": f"OAuth {user_access_token}",
|
|
365
|
+
},
|
|
366
|
+
json=data,
|
|
367
|
+
timeout=REQUESTS_TIMEOUT,
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
resp.raise_for_status()
|
|
371
|
+
|
|
372
|
+
return resp
|