mapillary-tools 0.14.0a1__py3-none-any.whl → 0.14.0b1__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 +5 -4
- mapillary_tools/authenticate.py +9 -9
- mapillary_tools/blackvue_parser.py +79 -22
- mapillary_tools/camm/camm_parser.py +5 -5
- mapillary_tools/commands/__main__.py +1 -2
- mapillary_tools/config.py +41 -18
- mapillary_tools/constants.py +3 -2
- mapillary_tools/exceptions.py +1 -1
- mapillary_tools/exif_read.py +65 -65
- mapillary_tools/exif_write.py +7 -7
- mapillary_tools/exiftool_read.py +23 -46
- mapillary_tools/exiftool_read_video.py +88 -49
- mapillary_tools/exiftool_runner.py +4 -24
- mapillary_tools/ffmpeg.py +417 -242
- mapillary_tools/geo.py +4 -21
- mapillary_tools/geotag/__init__.py +0 -1
- mapillary_tools/geotag/{geotag_from_generic.py → base.py} +34 -50
- mapillary_tools/geotag/factory.py +105 -103
- mapillary_tools/geotag/geotag_images_from_exif.py +15 -51
- mapillary_tools/geotag/geotag_images_from_exiftool.py +118 -63
- mapillary_tools/geotag/geotag_images_from_gpx.py +33 -16
- mapillary_tools/geotag/geotag_images_from_gpx_file.py +2 -34
- mapillary_tools/geotag/geotag_images_from_nmea_file.py +0 -3
- mapillary_tools/geotag/geotag_images_from_video.py +51 -14
- mapillary_tools/geotag/geotag_videos_from_exiftool.py +123 -0
- mapillary_tools/geotag/geotag_videos_from_gpx.py +35 -123
- mapillary_tools/geotag/geotag_videos_from_video.py +14 -147
- mapillary_tools/geotag/image_extractors/base.py +18 -0
- mapillary_tools/geotag/image_extractors/exif.py +60 -0
- mapillary_tools/geotag/image_extractors/exiftool.py +18 -0
- mapillary_tools/geotag/options.py +26 -3
- mapillary_tools/geotag/utils.py +62 -0
- mapillary_tools/geotag/video_extractors/base.py +18 -0
- mapillary_tools/geotag/video_extractors/exiftool.py +70 -0
- mapillary_tools/geotag/video_extractors/gpx.py +116 -0
- mapillary_tools/geotag/video_extractors/native.py +135 -0
- mapillary_tools/gpmf/gpmf_parser.py +16 -16
- mapillary_tools/gpmf/gps_filter.py +5 -3
- mapillary_tools/history.py +8 -3
- mapillary_tools/mp4/construct_mp4_parser.py +9 -8
- mapillary_tools/mp4/mp4_sample_parser.py +27 -27
- mapillary_tools/mp4/simple_mp4_builder.py +10 -9
- mapillary_tools/mp4/simple_mp4_parser.py +13 -12
- mapillary_tools/process_geotag_properties.py +21 -15
- mapillary_tools/process_sequence_properties.py +49 -49
- mapillary_tools/sample_video.py +15 -14
- mapillary_tools/serializer/description.py +587 -0
- mapillary_tools/serializer/gpx.py +132 -0
- mapillary_tools/telemetry.py +6 -5
- mapillary_tools/types.py +64 -635
- mapillary_tools/upload.py +176 -197
- mapillary_tools/upload_api_v4.py +94 -51
- mapillary_tools/uploader.py +284 -138
- mapillary_tools/utils.py +16 -18
- {mapillary_tools-0.14.0a1.dist-info → mapillary_tools-0.14.0b1.dist-info}/METADATA +87 -31
- mapillary_tools-0.14.0b1.dist-info/RECORD +75 -0
- {mapillary_tools-0.14.0a1.dist-info → mapillary_tools-0.14.0b1.dist-info}/WHEEL +1 -1
- mapillary_tools/geotag/geotag_images_from_exiftool_both_image_and_video.py +0 -77
- mapillary_tools/geotag/geotag_videos_from_exiftool_video.py +0 -151
- mapillary_tools/video_data_extraction/cli_options.py +0 -22
- mapillary_tools/video_data_extraction/extract_video_data.py +0 -157
- mapillary_tools/video_data_extraction/extractors/base_parser.py +0 -75
- mapillary_tools/video_data_extraction/extractors/blackvue_parser.py +0 -49
- mapillary_tools/video_data_extraction/extractors/camm_parser.py +0 -62
- mapillary_tools/video_data_extraction/extractors/exiftool_runtime_parser.py +0 -74
- mapillary_tools/video_data_extraction/extractors/exiftool_xml_parser.py +0 -52
- mapillary_tools/video_data_extraction/extractors/generic_video_parser.py +0 -52
- mapillary_tools/video_data_extraction/extractors/gopro_parser.py +0 -58
- mapillary_tools/video_data_extraction/extractors/gpx_parser.py +0 -108
- mapillary_tools/video_data_extraction/extractors/nmea_parser.py +0 -24
- mapillary_tools/video_data_extraction/video_data_parser_factory.py +0 -39
- mapillary_tools-0.14.0a1.dist-info/RECORD +0 -78
- {mapillary_tools-0.14.0a1.dist-info → mapillary_tools-0.14.0b1.dist-info}/entry_points.txt +0 -0
- {mapillary_tools-0.14.0a1.dist-info → mapillary_tools-0.14.0b1.dist-info}/licenses/LICENSE +0 -0
- {mapillary_tools-0.14.0a1.dist-info → mapillary_tools-0.14.0b1.dist-info}/top_level.txt +0 -0
mapillary_tools/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
VERSION = "0.14.
|
|
1
|
+
VERSION = "0.14.0b1"
|
mapillary_tools/api_v4.py
CHANGED
|
@@ -25,6 +25,7 @@ class ClusterFileType(enum.Enum):
|
|
|
25
25
|
ZIP = "zip"
|
|
26
26
|
BLACKVUE = "mly_blackvue_video"
|
|
27
27
|
CAMM = "mly_camm_video"
|
|
28
|
+
MLY_BUNDLE_MANIFEST = "mly_bundle_manifest"
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
class HTTPSystemCertsAdapter(HTTPAdapter):
|
|
@@ -135,7 +136,7 @@ def _log_debug_response(resp: requests.Response):
|
|
|
135
136
|
if logging.getLogger().getEffectiveLevel() <= logging.DEBUG:
|
|
136
137
|
return
|
|
137
138
|
|
|
138
|
-
data:
|
|
139
|
+
data: str | bytes
|
|
139
140
|
try:
|
|
140
141
|
data = _truncate(dumps(_sanitize(resp.json())))
|
|
141
142
|
except Exception:
|
|
@@ -148,7 +149,7 @@ def readable_http_error(ex: requests.HTTPError) -> str:
|
|
|
148
149
|
req = ex.request
|
|
149
150
|
resp = ex.response
|
|
150
151
|
|
|
151
|
-
data:
|
|
152
|
+
data: str | bytes
|
|
152
153
|
try:
|
|
153
154
|
data = _truncate(dumps(_sanitize(resp.json())))
|
|
154
155
|
except Exception:
|
|
@@ -284,7 +285,7 @@ def get_upload_token(email: str, password: str) -> requests.Response:
|
|
|
284
285
|
|
|
285
286
|
|
|
286
287
|
def fetch_organization(
|
|
287
|
-
user_access_token: str, organization_id:
|
|
288
|
+
user_access_token: str, organization_id: int | str
|
|
288
289
|
) -> requests.Response:
|
|
289
290
|
resp = request_get(
|
|
290
291
|
f"{MAPILLARY_GRAPH_API_ENDPOINT}/{organization_id}",
|
|
@@ -329,7 +330,7 @@ ActionType = T.Literal[
|
|
|
329
330
|
]
|
|
330
331
|
|
|
331
332
|
|
|
332
|
-
def log_event(action_type: ActionType, properties:
|
|
333
|
+
def log_event(action_type: ActionType, properties: dict) -> requests.Response:
|
|
333
334
|
resp = request_post(
|
|
334
335
|
f"{MAPILLARY_GRAPH_API_ENDPOINT}/logging",
|
|
335
336
|
json={
|
mapillary_tools/authenticate.py
CHANGED
|
@@ -11,7 +11,7 @@ import jsonschema
|
|
|
11
11
|
|
|
12
12
|
import requests
|
|
13
13
|
|
|
14
|
-
from . import api_v4, config, constants, exceptions
|
|
14
|
+
from . import api_v4, config, constants, exceptions
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
LOG = logging.getLogger(__name__)
|
|
@@ -64,7 +64,7 @@ def authenticate(
|
|
|
64
64
|
LOG.info('Creating new profile: "%s"', profile_name)
|
|
65
65
|
|
|
66
66
|
if jwt:
|
|
67
|
-
user_items:
|
|
67
|
+
user_items: config.UserItem = {"user_upload_token": jwt}
|
|
68
68
|
user_items = _verify_user_auth(_validate_profile(user_items))
|
|
69
69
|
else:
|
|
70
70
|
user_items = _prompt_login(
|
|
@@ -89,7 +89,7 @@ def authenticate(
|
|
|
89
89
|
def fetch_user_items(
|
|
90
90
|
user_name: str | None = None,
|
|
91
91
|
organization_key: str | None = None,
|
|
92
|
-
) ->
|
|
92
|
+
) -> config.UserItem:
|
|
93
93
|
"""
|
|
94
94
|
Read user information from the config file,
|
|
95
95
|
or prompt the user to authenticate if the specified profile does not exist
|
|
@@ -155,9 +155,9 @@ def _prompt(message: str) -> str:
|
|
|
155
155
|
return input()
|
|
156
156
|
|
|
157
157
|
|
|
158
|
-
def _validate_profile(user_items:
|
|
158
|
+
def _validate_profile(user_items: config.UserItem) -> config.UserItem:
|
|
159
159
|
try:
|
|
160
|
-
jsonschema.validate(user_items,
|
|
160
|
+
jsonschema.validate(user_items, config.UserItemSchema)
|
|
161
161
|
except jsonschema.ValidationError as ex:
|
|
162
162
|
raise exceptions.MapillaryBadParameterError(
|
|
163
163
|
f"Invalid profile format: {ex.message}"
|
|
@@ -165,7 +165,7 @@ def _validate_profile(user_items: types.UserItem) -> types.UserItem:
|
|
|
165
165
|
return user_items
|
|
166
166
|
|
|
167
167
|
|
|
168
|
-
def _verify_user_auth(user_items:
|
|
168
|
+
def _verify_user_auth(user_items: config.UserItem) -> config.UserItem:
|
|
169
169
|
"""
|
|
170
170
|
Verify that the user access token is valid
|
|
171
171
|
"""
|
|
@@ -205,7 +205,7 @@ def _validate_profile_name(profile_name: str):
|
|
|
205
205
|
)
|
|
206
206
|
|
|
207
207
|
|
|
208
|
-
def _list_all_profiles(profiles: dict[str,
|
|
208
|
+
def _list_all_profiles(profiles: dict[str, config.UserItem]) -> None:
|
|
209
209
|
_echo("Existing Mapillary profiles:")
|
|
210
210
|
|
|
211
211
|
# Header
|
|
@@ -256,7 +256,7 @@ def _is_login_retryable(ex: requests.HTTPError) -> bool:
|
|
|
256
256
|
def _prompt_login(
|
|
257
257
|
user_email: str | None = None,
|
|
258
258
|
user_password: str | None = None,
|
|
259
|
-
) ->
|
|
259
|
+
) -> config.UserItem:
|
|
260
260
|
_enabled = _prompt_enabled()
|
|
261
261
|
|
|
262
262
|
if user_email is None:
|
|
@@ -288,7 +288,7 @@ def _prompt_login(
|
|
|
288
288
|
|
|
289
289
|
data = resp.json()
|
|
290
290
|
|
|
291
|
-
user_items:
|
|
291
|
+
user_items: config.UserItem = {
|
|
292
292
|
"user_upload_token": str(data["access_token"]),
|
|
293
293
|
"MAPSettingsUserKey": str(data["user_id"]),
|
|
294
294
|
}
|
|
@@ -14,15 +14,14 @@ from .mp4 import simple_mp4_parser as sparser
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
LOG = logging.getLogger(__name__)
|
|
17
|
-
# An example: [1623057074211]$GPVTG,,T,,M,0.078,N,0.144,K,D*28[1623057075215]
|
|
18
17
|
NMEA_LINE_REGEX = re.compile(
|
|
19
18
|
rb"""
|
|
20
19
|
^\s*
|
|
21
|
-
\[(\d+)\] #
|
|
20
|
+
\[(\d+)\] # Timestamp
|
|
22
21
|
\s*
|
|
23
|
-
(\$\w{5}.*) #
|
|
22
|
+
(\$\w{5}.*) # NMEA message
|
|
24
23
|
\s*
|
|
25
|
-
(\[\d+\])? #
|
|
24
|
+
(\[\d+\])? # Strange timestamp
|
|
26
25
|
\s*$
|
|
27
26
|
""",
|
|
28
27
|
re.X,
|
|
@@ -47,7 +46,7 @@ def extract_blackvue_info(fp: T.BinaryIO) -> BlackVueInfo | None:
|
|
|
47
46
|
if gps_data is None:
|
|
48
47
|
return None
|
|
49
48
|
|
|
50
|
-
points =
|
|
49
|
+
points = _parse_gps_box(gps_data)
|
|
51
50
|
points.sort(key=lambda p: p.time)
|
|
52
51
|
|
|
53
52
|
if points:
|
|
@@ -83,8 +82,12 @@ def extract_camera_model(fp: T.BinaryIO) -> str:
|
|
|
83
82
|
|
|
84
83
|
|
|
85
84
|
def _extract_camera_model_from_cprt(cprt_bytes: bytes) -> str:
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
"""
|
|
86
|
+
>>> _extract_camera_model_from_cprt(b' {"model":"DR900X Plus","ver":0.918,"lang":"English","direct":1,"psn":"","temp":34,"GPS":1}')
|
|
87
|
+
'DR900X Plus'
|
|
88
|
+
>>> _extract_camera_model_from_cprt(b' Pittasoft Co., Ltd.;DR900S-1CH;1.008;English;1;D90SS1HAE00661;T69;')
|
|
89
|
+
'DR900S-1CH'
|
|
90
|
+
"""
|
|
88
91
|
cprt_bytes = cprt_bytes.strip().strip(b"\x00")
|
|
89
92
|
|
|
90
93
|
try:
|
|
@@ -111,28 +114,82 @@ def _extract_camera_model_from_cprt(cprt_bytes: bytes) -> str:
|
|
|
111
114
|
return ""
|
|
112
115
|
|
|
113
116
|
|
|
114
|
-
def _parse_gps_box(gps_data: bytes) ->
|
|
117
|
+
def _parse_gps_box(gps_data: bytes) -> list[geo.Point]:
|
|
118
|
+
"""
|
|
119
|
+
>>> list(_parse_gps_box(b"[1623057074211]$GPGGA,202530.00,5109.0262,N,11401.8407,W,5,40,0.5,1097.36,M,-17.00,M,18,TSTR*61"))
|
|
120
|
+
[Point(time=1623057074211, lat=51.150436666666664, lon=-114.03067833333333, alt=1097.36, angle=None)]
|
|
121
|
+
|
|
122
|
+
>>> list(_parse_gps_box(b"[1629874404069]$GNGGA,175322.00,3244.53126,N,11710.97811,W,1,12,0.84,17.4,M,-34.0,M,,*45"))
|
|
123
|
+
[Point(time=1629874404069, lat=32.742187666666666, lon=-117.1829685, alt=17.4, angle=None)]
|
|
124
|
+
|
|
125
|
+
>>> list(_parse_gps_box(b"[1629874404069]$GNGLL,4404.14012,N,12118.85993,W,001037.00,A,A*67"))
|
|
126
|
+
[Point(time=1629874404069, lat=44.069002, lon=-121.31433216666667, alt=None, angle=None)]
|
|
127
|
+
|
|
128
|
+
>>> list(_parse_gps_box(b"[1629874404069]$GNRMC,001031.00,A,4404.13993,N,12118.86023,W,0.146,,100117,,,A*7B"))
|
|
129
|
+
[Point(time=1629874404069, lat=44.06899883333333, lon=-121.31433716666666, alt=None, angle=None)]
|
|
130
|
+
|
|
131
|
+
>>> list(_parse_gps_box(b"[1623057074211]$GPVTG,,T,,M,0.078,N,0.144,K,D*28[1623057075215]"))
|
|
132
|
+
[]
|
|
133
|
+
"""
|
|
134
|
+
points_by_sentence_type: dict[str, list[geo.Point]] = {}
|
|
135
|
+
|
|
115
136
|
for line_bytes in gps_data.splitlines():
|
|
116
137
|
match = NMEA_LINE_REGEX.match(line_bytes)
|
|
117
138
|
if match is None:
|
|
118
139
|
continue
|
|
119
140
|
nmea_line_bytes = match.group(2)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
141
|
+
|
|
142
|
+
if not nmea_line_bytes:
|
|
143
|
+
continue
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
nmea_line = nmea_line_bytes.decode("utf8")
|
|
147
|
+
except UnicodeDecodeError:
|
|
148
|
+
continue
|
|
149
|
+
|
|
150
|
+
if not nmea_line:
|
|
151
|
+
continue
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
message = pynmea2.parse(nmea_line)
|
|
155
|
+
except pynmea2.nmea.ParseError:
|
|
156
|
+
continue
|
|
157
|
+
|
|
158
|
+
epoch_ms = int(match.group(1))
|
|
159
|
+
|
|
160
|
+
# https://tavotech.com/gps-nmea-sentence-structure/
|
|
161
|
+
if message.sentence_type in ["GGA"]:
|
|
162
|
+
if not message.is_valid:
|
|
128
163
|
continue
|
|
129
|
-
|
|
164
|
+
point = geo.Point(
|
|
165
|
+
time=epoch_ms,
|
|
166
|
+
lat=message.latitude,
|
|
167
|
+
lon=message.longitude,
|
|
168
|
+
alt=message.altitude,
|
|
169
|
+
angle=None,
|
|
170
|
+
)
|
|
171
|
+
points_by_sentence_type.setdefault(message.sentence_type, []).append(point)
|
|
172
|
+
|
|
173
|
+
elif message.sentence_type in ["RMC", "GLL"]:
|
|
174
|
+
if not message.is_valid:
|
|
130
175
|
continue
|
|
131
|
-
|
|
132
|
-
yield geo.Point(
|
|
176
|
+
point = geo.Point(
|
|
133
177
|
time=epoch_ms,
|
|
134
|
-
lat=
|
|
135
|
-
lon=
|
|
136
|
-
alt=
|
|
178
|
+
lat=message.latitude,
|
|
179
|
+
lon=message.longitude,
|
|
180
|
+
alt=None,
|
|
137
181
|
angle=None,
|
|
138
182
|
)
|
|
183
|
+
points_by_sentence_type.setdefault(message.sentence_type, []).append(point)
|
|
184
|
+
|
|
185
|
+
# This is the extraction order in exiftool
|
|
186
|
+
if "RMC" in points_by_sentence_type:
|
|
187
|
+
return points_by_sentence_type["RMC"]
|
|
188
|
+
|
|
189
|
+
if "GGA" in points_by_sentence_type:
|
|
190
|
+
return points_by_sentence_type["GGA"]
|
|
191
|
+
|
|
192
|
+
if "GLL" in points_by_sentence_type:
|
|
193
|
+
return points_by_sentence_type["GLL"]
|
|
194
|
+
|
|
195
|
+
return []
|
|
@@ -373,7 +373,7 @@ SAMPLE_ENTRY_CLS_BY_CAMM_TYPE = {
|
|
|
373
373
|
assert len(SAMPLE_ENTRY_CLS_BY_CAMM_TYPE) == 5, SAMPLE_ENTRY_CLS_BY_CAMM_TYPE.keys()
|
|
374
374
|
|
|
375
375
|
|
|
376
|
-
_SWITCH:
|
|
376
|
+
_SWITCH: dict[int, C.Struct] = {
|
|
377
377
|
# Angle_axis
|
|
378
378
|
CAMMType.ANGLE_AXIS.value: _Float[3], # type: ignore
|
|
379
379
|
# Exposure time
|
|
@@ -436,7 +436,7 @@ def _parse_telemetry_from_sample(
|
|
|
436
436
|
|
|
437
437
|
def _filter_telemetry_by_elst_segments(
|
|
438
438
|
measurements: T.Iterable[TelemetryMeasurement],
|
|
439
|
-
elst: T.Sequence[
|
|
439
|
+
elst: T.Sequence[tuple[float, float]],
|
|
440
440
|
) -> T.Generator[TelemetryMeasurement, None, None]:
|
|
441
441
|
empty_elst = [entry for entry in elst if entry[0] == -1]
|
|
442
442
|
if empty_elst:
|
|
@@ -466,8 +466,8 @@ def _filter_telemetry_by_elst_segments(
|
|
|
466
466
|
|
|
467
467
|
|
|
468
468
|
def elst_entry_to_seconds(
|
|
469
|
-
entry:
|
|
470
|
-
) ->
|
|
469
|
+
entry: dict, movie_timescale: int, media_timescale: int
|
|
470
|
+
) -> tuple[float, float]:
|
|
471
471
|
assert movie_timescale > 0, "expected positive movie_timescale"
|
|
472
472
|
assert media_timescale > 0, "expected positive media_timescale"
|
|
473
473
|
media_time, duration = entry["media_time"], entry["segment_duration"]
|
|
@@ -477,7 +477,7 @@ def elst_entry_to_seconds(
|
|
|
477
477
|
return (media_time, duration)
|
|
478
478
|
|
|
479
479
|
|
|
480
|
-
def _is_camm_description(description:
|
|
480
|
+
def _is_camm_description(description: dict) -> bool:
|
|
481
481
|
return description["format"] == b"camm"
|
|
482
482
|
|
|
483
483
|
|
|
@@ -2,7 +2,6 @@ import argparse
|
|
|
2
2
|
import enum
|
|
3
3
|
import logging
|
|
4
4
|
import sys
|
|
5
|
-
import typing as T
|
|
6
5
|
from pathlib import Path
|
|
7
6
|
|
|
8
7
|
import requests
|
|
@@ -86,7 +85,7 @@ def configure_logger(logger: logging.Logger, stream=None) -> None:
|
|
|
86
85
|
logger.addHandler(handler)
|
|
87
86
|
|
|
88
87
|
|
|
89
|
-
def _log_params(argvars:
|
|
88
|
+
def _log_params(argvars: dict) -> None:
|
|
90
89
|
MAX_ENTRIES = 5
|
|
91
90
|
|
|
92
91
|
def _stringify(x) -> str:
|
mapillary_tools/config.py
CHANGED
|
@@ -1,30 +1,55 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import configparser
|
|
2
4
|
import os
|
|
5
|
+
import sys
|
|
3
6
|
import typing as T
|
|
7
|
+
from typing import TypedDict
|
|
4
8
|
|
|
5
|
-
|
|
6
|
-
|
|
9
|
+
if sys.version_info >= (3, 11):
|
|
10
|
+
from typing import Required
|
|
11
|
+
else:
|
|
12
|
+
from typing_extensions import Required
|
|
7
13
|
|
|
8
|
-
|
|
9
|
-
# Windows is not happy with | so we convert MLY|ID|TOKEN to MLY_ID_TOKEN
|
|
10
|
-
_CLIENT_ID = _CLIENT_ID.replace("|", "_", 2)
|
|
14
|
+
from . import api_v4
|
|
11
15
|
|
|
12
|
-
DEFAULT_MAPILLARY_FOLDER = os.path.join(
|
|
13
|
-
os.path.expanduser("~"),
|
|
14
|
-
".config",
|
|
15
|
-
"mapillary",
|
|
16
|
-
)
|
|
17
16
|
|
|
17
|
+
DEFAULT_MAPILLARY_FOLDER = os.path.join(os.path.expanduser("~"), ".config", "mapillary")
|
|
18
18
|
MAPILLARY_CONFIG_PATH = os.getenv(
|
|
19
19
|
"MAPILLARY_CONFIG_PATH",
|
|
20
20
|
os.path.join(
|
|
21
21
|
DEFAULT_MAPILLARY_FOLDER,
|
|
22
22
|
"configs",
|
|
23
|
-
|
|
23
|
+
# Windows is not happy with | so we convert MLY|ID|TOKEN to MLY_ID_TOKEN
|
|
24
|
+
api_v4.MAPILLARY_CLIENT_TOKEN.replace("|", "_"),
|
|
24
25
|
),
|
|
25
26
|
)
|
|
26
27
|
|
|
27
28
|
|
|
29
|
+
class UserItem(TypedDict, total=False):
|
|
30
|
+
MAPOrganizationKey: int | str
|
|
31
|
+
# Username
|
|
32
|
+
MAPSettingsUsername: str
|
|
33
|
+
# User ID
|
|
34
|
+
MAPSettingsUserKey: str
|
|
35
|
+
# User access token
|
|
36
|
+
user_upload_token: Required[str]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
UserItemSchema = {
|
|
40
|
+
"type": "object",
|
|
41
|
+
"properties": {
|
|
42
|
+
"MAPOrganizationKey": {"type": ["integer", "string"]},
|
|
43
|
+
# Not in use. Keep here for back-compatibility
|
|
44
|
+
"MAPSettingsUsername": {"type": "string"},
|
|
45
|
+
"MAPSettingsUserKey": {"type": "string"},
|
|
46
|
+
"user_upload_token": {"type": "string"},
|
|
47
|
+
},
|
|
48
|
+
"required": ["user_upload_token"],
|
|
49
|
+
"additionalProperties": True,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
28
53
|
def _load_config(config_path: str) -> configparser.ConfigParser:
|
|
29
54
|
config = configparser.ConfigParser()
|
|
30
55
|
# Override to not change option names (by default it will lower them)
|
|
@@ -34,19 +59,17 @@ def _load_config(config_path: str) -> configparser.ConfigParser:
|
|
|
34
59
|
return config
|
|
35
60
|
|
|
36
61
|
|
|
37
|
-
def load_user(
|
|
38
|
-
profile_name: str, config_path: T.Optional[str] = None
|
|
39
|
-
) -> T.Optional[types.UserItem]:
|
|
62
|
+
def load_user(profile_name: str, config_path: str | None = None) -> UserItem | None:
|
|
40
63
|
if config_path is None:
|
|
41
64
|
config_path = MAPILLARY_CONFIG_PATH
|
|
42
65
|
config = _load_config(config_path)
|
|
43
66
|
if not config.has_section(profile_name):
|
|
44
67
|
return None
|
|
45
68
|
user_items = dict(config.items(profile_name))
|
|
46
|
-
return T.cast(
|
|
69
|
+
return T.cast(UserItem, user_items)
|
|
47
70
|
|
|
48
71
|
|
|
49
|
-
def list_all_users(config_path:
|
|
72
|
+
def list_all_users(config_path: str | None = None) -> dict[str, UserItem]:
|
|
50
73
|
if config_path is None:
|
|
51
74
|
config_path = MAPILLARY_CONFIG_PATH
|
|
52
75
|
cp = _load_config(config_path)
|
|
@@ -58,7 +81,7 @@ def list_all_users(config_path: T.Optional[str] = None) -> T.Dict[str, types.Use
|
|
|
58
81
|
|
|
59
82
|
|
|
60
83
|
def update_config(
|
|
61
|
-
profile_name: str, user_items:
|
|
84
|
+
profile_name: str, user_items: UserItem, config_path: str | None = None
|
|
62
85
|
) -> None:
|
|
63
86
|
if config_path is None:
|
|
64
87
|
config_path = MAPILLARY_CONFIG_PATH
|
|
@@ -72,7 +95,7 @@ def update_config(
|
|
|
72
95
|
config.write(fp)
|
|
73
96
|
|
|
74
97
|
|
|
75
|
-
def remove_config(profile_name: str, config_path:
|
|
98
|
+
def remove_config(profile_name: str, config_path: str | None = None) -> None:
|
|
76
99
|
if config_path is None:
|
|
77
100
|
config_path = MAPILLARY_CONFIG_PATH
|
|
78
101
|
|
mapillary_tools/constants.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
-
import typing as T
|
|
5
4
|
|
|
6
5
|
import appdirs
|
|
7
6
|
|
|
@@ -52,7 +51,7 @@ UPLOAD_CHUNK_SIZE_MB = float(os.getenv(_ENV_PREFIX + "UPLOAD_CHUNK_SIZE_MB", 1))
|
|
|
52
51
|
# It is used to filter out noisy points
|
|
53
52
|
GOPRO_MAX_DOP100 = int(os.getenv(_ENV_PREFIX + "GOPRO_MAX_DOP100", 1000))
|
|
54
53
|
# Within the GPS stream: 0 - no lock, 2 or 3 - 2D or 3D Lock
|
|
55
|
-
GOPRO_GPS_FIXES:
|
|
54
|
+
GOPRO_GPS_FIXES: set[int] = set(
|
|
56
55
|
int(fix) for fix in os.getenv(_ENV_PREFIX + "GOPRO_GPS_FIXES", "2,3").split(",")
|
|
57
56
|
)
|
|
58
57
|
MAX_UPLOAD_RETRIES: int = int(os.getenv(_ENV_PREFIX + "MAX_UPLOAD_RETRIES", 200))
|
|
@@ -90,3 +89,5 @@ MAPILLARY_UPLOAD_HISTORY_PATH: str = os.getenv(
|
|
|
90
89
|
"upload_history",
|
|
91
90
|
),
|
|
92
91
|
)
|
|
92
|
+
|
|
93
|
+
MAX_IMAGE_UPLOAD_WORKERS = int(os.getenv(_ENV_PREFIX + "MAX_IMAGE_UPLOAD_WORKERS", 64))
|
mapillary_tools/exceptions.py
CHANGED