mapillary-tools 0.13.3a1__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.
- mapillary_tools/__init__.py +1 -1
- mapillary_tools/api_v4.py +287 -22
- mapillary_tools/authenticate.py +326 -64
- mapillary_tools/blackvue_parser.py +195 -0
- mapillary_tools/camm/camm_builder.py +55 -97
- mapillary_tools/camm/camm_parser.py +429 -181
- mapillary_tools/commands/__main__.py +17 -8
- 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 +44 -13
- mapillary_tools/commands/video_process_and_upload.py +19 -5
- mapillary_tools/config.py +65 -26
- mapillary_tools/constants.py +141 -18
- mapillary_tools/exceptions.py +37 -34
- mapillary_tools/exif_read.py +221 -116
- mapillary_tools/exif_write.py +10 -8
- mapillary_tools/exiftool_read.py +33 -42
- mapillary_tools/exiftool_read_video.py +97 -47
- mapillary_tools/exiftool_runner.py +57 -0
- mapillary_tools/ffmpeg.py +417 -242
- mapillary_tools/geo.py +158 -118
- mapillary_tools/geotag/__init__.py +0 -1
- mapillary_tools/geotag/base.py +147 -0
- mapillary_tools/geotag/factory.py +307 -0
- mapillary_tools/geotag/geotag_images_from_exif.py +14 -131
- mapillary_tools/geotag/geotag_images_from_exiftool.py +136 -85
- mapillary_tools/geotag/geotag_images_from_gpx.py +60 -124
- mapillary_tools/geotag/geotag_images_from_gpx_file.py +13 -126
- mapillary_tools/geotag/geotag_images_from_nmea_file.py +4 -5
- mapillary_tools/geotag/geotag_images_from_video.py +88 -51
- mapillary_tools/geotag/geotag_videos_from_exiftool.py +123 -0
- mapillary_tools/geotag/geotag_videos_from_gpx.py +52 -0
- mapillary_tools/geotag/geotag_videos_from_video.py +20 -185
- 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 +182 -0
- mapillary_tools/geotag/utils.py +52 -16
- 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 +160 -0
- mapillary_tools/{geotag → gpmf}/gpmf_parser.py +205 -182
- mapillary_tools/{geotag → gpmf}/gps_filter.py +5 -3
- mapillary_tools/history.py +134 -20
- mapillary_tools/mp4/construct_mp4_parser.py +17 -10
- mapillary_tools/mp4/io_utils.py +0 -1
- mapillary_tools/mp4/mp4_sample_parser.py +36 -28
- mapillary_tools/mp4/simple_mp4_builder.py +10 -9
- mapillary_tools/mp4/simple_mp4_parser.py +13 -22
- mapillary_tools/process_geotag_properties.py +184 -414
- mapillary_tools/process_sequence_properties.py +594 -225
- mapillary_tools/sample_video.py +20 -26
- mapillary_tools/serializer/description.py +587 -0
- mapillary_tools/serializer/gpx.py +132 -0
- mapillary_tools/telemetry.py +26 -13
- mapillary_tools/types.py +98 -611
- mapillary_tools/upload.py +408 -416
- mapillary_tools/upload_api_v4.py +172 -174
- mapillary_tools/uploader.py +804 -284
- mapillary_tools/utils.py +49 -18
- {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0.dist-info}/METADATA +93 -35
- mapillary_tools-0.14.0.dist-info/RECORD +75 -0
- {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0.dist-info}/WHEEL +1 -1
- mapillary_tools/geotag/blackvue_parser.py +0 -118
- mapillary_tools/geotag/geotag_from_generic.py +0 -22
- mapillary_tools/geotag/geotag_images_from_exiftool_both_image_and_video.py +0 -93
- mapillary_tools/geotag/geotag_videos_from_exiftool_video.py +0 -145
- mapillary_tools/video_data_extraction/cli_options.py +0 -22
- mapillary_tools/video_data_extraction/extract_video_data.py +0 -176
- mapillary_tools/video_data_extraction/extractors/base_parser.py +0 -75
- mapillary_tools/video_data_extraction/extractors/blackvue_parser.py +0 -34
- mapillary_tools/video_data_extraction/extractors/camm_parser.py +0 -38
- mapillary_tools/video_data_extraction/extractors/exiftool_runtime_parser.py +0 -71
- mapillary_tools/video_data_extraction/extractors/exiftool_xml_parser.py +0 -53
- mapillary_tools/video_data_extraction/extractors/generic_video_parser.py +0 -52
- mapillary_tools/video_data_extraction/extractors/gopro_parser.py +0 -43
- 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.13.3a1.dist-info/RECORD +0 -75
- /mapillary_tools/{geotag → gpmf}/gpmf_gps_filter.py +0 -0
- {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0.dist-info}/entry_points.txt +0 -0
- {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0.dist-info/licenses}/LICENSE +0 -0
- {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0.dist-info}/top_level.txt +0 -0
|
@@ -2,10 +2,12 @@ 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
|
|
8
|
+
|
|
9
|
+
from .. import api_v4, constants, exceptions, VERSION
|
|
10
|
+
from ..upload import log_exception
|
|
9
11
|
from . import (
|
|
10
12
|
authenticate,
|
|
11
13
|
process,
|
|
@@ -84,7 +86,7 @@ def configure_logger(logger: logging.Logger, stream=None) -> None:
|
|
|
84
86
|
logger.addHandler(handler)
|
|
85
87
|
|
|
86
88
|
|
|
87
|
-
def _log_params(argvars:
|
|
89
|
+
def _log_params(argvars: dict) -> None:
|
|
88
90
|
MAX_ENTRIES = 5
|
|
89
91
|
|
|
90
92
|
def _stringify(x) -> str:
|
|
@@ -160,11 +162,18 @@ def main():
|
|
|
160
162
|
|
|
161
163
|
try:
|
|
162
164
|
args.func(argvars)
|
|
163
|
-
except
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
)
|
|
167
|
-
|
|
165
|
+
except requests.HTTPError as ex:
|
|
166
|
+
log_exception(ex)
|
|
167
|
+
# TODO: standardize exit codes as exceptions.MapillaryUserError
|
|
168
|
+
sys.exit(16)
|
|
169
|
+
|
|
170
|
+
except api_v4.HTTPContentError as ex:
|
|
171
|
+
log_exception(ex)
|
|
172
|
+
sys.exit(17)
|
|
173
|
+
|
|
174
|
+
except exceptions.MapillaryUserError as ex:
|
|
175
|
+
log_exception(ex)
|
|
176
|
+
sys.exit(ex.exit_code)
|
|
168
177
|
|
|
169
178
|
|
|
170
179
|
if __name__ == "__main__":
|
|
@@ -10,7 +10,7 @@ class Command:
|
|
|
10
10
|
|
|
11
11
|
def add_basic_arguments(self, parser: argparse.ArgumentParser):
|
|
12
12
|
parser.add_argument(
|
|
13
|
-
"--user_name", help="Mapillary user
|
|
13
|
+
"--user_name", help="Mapillary user profile", default=None, required=False
|
|
14
14
|
)
|
|
15
15
|
parser.add_argument(
|
|
16
16
|
"--user_email",
|
|
@@ -27,6 +27,13 @@ class Command:
|
|
|
27
27
|
parser.add_argument(
|
|
28
28
|
"--jwt", help="Mapillary user access token", default=None, required=False
|
|
29
29
|
)
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
"--delete",
|
|
32
|
+
help="Delete the specified user profile",
|
|
33
|
+
default=False,
|
|
34
|
+
required=False,
|
|
35
|
+
action="store_true",
|
|
36
|
+
)
|
|
30
37
|
|
|
31
38
|
def run(self, vars_args: dict):
|
|
32
39
|
authenticate(
|
|
@@ -1,41 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import argparse
|
|
2
4
|
import inspect
|
|
3
|
-
import typing as T
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
|
|
6
|
-
from .. import constants
|
|
7
|
+
from .. import constants, types
|
|
7
8
|
from ..process_geotag_properties import (
|
|
8
|
-
|
|
9
|
-
GeotagSource,
|
|
9
|
+
DEFAULT_GEOTAG_SOURCE_OPTIONS,
|
|
10
10
|
process_finalize,
|
|
11
11
|
process_geotag_properties,
|
|
12
|
+
SourceType,
|
|
12
13
|
)
|
|
13
14
|
from ..process_sequence_properties import process_sequence_properties
|
|
14
15
|
|
|
15
16
|
|
|
17
|
+
def bold_text(text: str) -> str:
|
|
18
|
+
ANSI_BOLD = "\033[1m"
|
|
19
|
+
ANSI_RESET_ALL = "\033[0m"
|
|
20
|
+
return f"{ANSI_BOLD}{text}{ANSI_RESET_ALL}"
|
|
21
|
+
|
|
22
|
+
|
|
16
23
|
class Command:
|
|
17
24
|
name = "process"
|
|
18
25
|
help = "process images and videos"
|
|
19
26
|
|
|
20
27
|
def add_basic_arguments(self, parser: argparse.ArgumentParser):
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"gpx",
|
|
28
|
-
"nmea",
|
|
28
|
+
geotag_gpx_based_sources: list[str] = [
|
|
29
|
+
SourceType.GPX.value,
|
|
30
|
+
SourceType.NMEA.value,
|
|
31
|
+
SourceType.GOPRO.value,
|
|
32
|
+
SourceType.BLACKVUE.value,
|
|
33
|
+
SourceType.CAMM.value,
|
|
29
34
|
]
|
|
30
|
-
geotag_gpx_based_sources: T.List[GeotagSource] = [
|
|
31
|
-
"gpx",
|
|
32
|
-
"gopro_videos",
|
|
33
|
-
"nmea",
|
|
34
|
-
"blackvue_videos",
|
|
35
|
-
"camm",
|
|
36
|
-
]
|
|
37
|
-
for source in geotag_gpx_based_sources:
|
|
38
|
-
assert source in geotag_sources
|
|
39
35
|
|
|
40
36
|
parser.add_argument(
|
|
41
37
|
"--skip_process_errors",
|
|
@@ -47,14 +43,12 @@ class Command:
|
|
|
47
43
|
parser.add_argument(
|
|
48
44
|
"--filetypes",
|
|
49
45
|
"--file_types",
|
|
50
|
-
help=f"Process files of the specified types only. Supported file types: {','.join(sorted(t.value for t in FileType))} [default: %(default)s]",
|
|
51
|
-
type=lambda option: set(FileType(t) for t in option.split(",")),
|
|
52
|
-
default=
|
|
46
|
+
help=f"Process files of the specified types only. Supported file types: {','.join(sorted(t.value for t in types.FileType))} [default: %(default)s]",
|
|
47
|
+
type=lambda option: set(types.FileType(t) for t in option.split(",")),
|
|
48
|
+
default=None,
|
|
53
49
|
required=False,
|
|
54
50
|
)
|
|
55
|
-
group = parser.add_argument_group(
|
|
56
|
-
f"{constants.ANSI_BOLD}PROCESS EXIF OPTIONS{constants.ANSI_RESET_ALL}"
|
|
57
|
-
)
|
|
51
|
+
group = parser.add_argument_group(bold_text("PROCESS EXIF OPTIONS"))
|
|
58
52
|
group.add_argument(
|
|
59
53
|
"--overwrite_all_EXIF_tags",
|
|
60
54
|
help="Overwrite all of the relevant EXIF tags with the values obtained in process. It is equivalent to supplying all the --overwrite_EXIF_*_tag flags.",
|
|
@@ -92,7 +86,7 @@ class Command:
|
|
|
92
86
|
)
|
|
93
87
|
|
|
94
88
|
group_metadata = parser.add_argument_group(
|
|
95
|
-
|
|
89
|
+
bold_text("PROCESS METADATA OPTIONS")
|
|
96
90
|
)
|
|
97
91
|
group_metadata.add_argument(
|
|
98
92
|
"--device_make",
|
|
@@ -108,7 +102,7 @@ class Command:
|
|
|
108
102
|
)
|
|
109
103
|
|
|
110
104
|
group_geotagging = parser.add_argument_group(
|
|
111
|
-
|
|
105
|
+
bold_text("PROCESS GEOTAGGING OPTIONS")
|
|
112
106
|
)
|
|
113
107
|
group_geotagging.add_argument(
|
|
114
108
|
"--desc_path",
|
|
@@ -118,10 +112,9 @@ class Command:
|
|
|
118
112
|
)
|
|
119
113
|
group_geotagging.add_argument(
|
|
120
114
|
"--geotag_source",
|
|
121
|
-
help="Provide the source of date/time and GPS information needed for geotagging. [default:
|
|
122
|
-
action="
|
|
123
|
-
|
|
124
|
-
default="exif",
|
|
115
|
+
help=f"Provide the source of date/time and GPS information needed for geotagging. Supported source types: {', '.join(g.value for g in SourceType)} [default: {','.join(DEFAULT_GEOTAG_SOURCE_OPTIONS)}]",
|
|
116
|
+
action="append",
|
|
117
|
+
default=[],
|
|
125
118
|
required=False,
|
|
126
119
|
)
|
|
127
120
|
group_geotagging.add_argument(
|
|
@@ -174,7 +167,7 @@ class Command:
|
|
|
174
167
|
)
|
|
175
168
|
|
|
176
169
|
group_sequence = parser.add_argument_group(
|
|
177
|
-
|
|
170
|
+
bold_text("PROCESS SEQUENCE OPTIONS")
|
|
178
171
|
)
|
|
179
172
|
group_sequence.add_argument(
|
|
180
173
|
"--cutoff_distance",
|
|
@@ -212,24 +205,7 @@ class Command:
|
|
|
212
205
|
)
|
|
213
206
|
|
|
214
207
|
def run(self, vars_args: dict):
|
|
215
|
-
if (
|
|
216
|
-
"geotag_source" in vars_args
|
|
217
|
-
and vars_args["geotag_source"] == "blackvue_videos"
|
|
218
|
-
and (
|
|
219
|
-
"device_make" not in vars_args
|
|
220
|
-
or ("device_make" in vars_args and not vars_args["device_make"])
|
|
221
|
-
)
|
|
222
|
-
):
|
|
223
|
-
vars_args["device_make"] = "Blackvue"
|
|
224
|
-
if (
|
|
225
|
-
"device_make" in vars_args
|
|
226
|
-
and vars_args["device_make"]
|
|
227
|
-
and vars_args["device_make"].lower() == "blackvue"
|
|
228
|
-
):
|
|
229
|
-
vars_args["duplicate_angle"] = 360
|
|
230
|
-
|
|
231
208
|
metadatas = process_geotag_properties(
|
|
232
|
-
vars_args=vars_args,
|
|
233
209
|
**(
|
|
234
210
|
{
|
|
235
211
|
k: v
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
|
|
3
|
+
from ..authenticate import fetch_user_items
|
|
4
|
+
|
|
1
5
|
from .process import Command as ProcessCommand
|
|
2
6
|
from .upload import Command as UploadCommand
|
|
3
7
|
|
|
@@ -10,10 +14,20 @@ class Command:
|
|
|
10
14
|
ProcessCommand().add_basic_arguments(parser)
|
|
11
15
|
UploadCommand().add_basic_arguments(parser)
|
|
12
16
|
|
|
13
|
-
def run(self,
|
|
14
|
-
if
|
|
17
|
+
def run(self, vars_args: dict):
|
|
18
|
+
if vars_args.get("desc_path") is None:
|
|
15
19
|
# \x00 is a special path similiar to /dev/null
|
|
16
20
|
# it tells process command do not write anything
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
vars_args["desc_path"] = "\x00"
|
|
22
|
+
|
|
23
|
+
if "user_items" not in vars_args:
|
|
24
|
+
vars_args["user_items"] = fetch_user_items(
|
|
25
|
+
**{
|
|
26
|
+
k: v
|
|
27
|
+
for k, v in vars_args.items()
|
|
28
|
+
if k in inspect.getfullargspec(fetch_user_items).args
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
ProcessCommand().run(vars_args)
|
|
33
|
+
UploadCommand().run(vars_args)
|
|
@@ -4,6 +4,7 @@ from pathlib import Path
|
|
|
4
4
|
|
|
5
5
|
from .. import constants
|
|
6
6
|
from ..sample_video import sample_video
|
|
7
|
+
from .process import bold_text
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class Command:
|
|
@@ -11,9 +12,7 @@ class Command:
|
|
|
11
12
|
help = "sample video into images"
|
|
12
13
|
|
|
13
14
|
def add_basic_arguments(self, parser: argparse.ArgumentParser):
|
|
14
|
-
group = parser.add_argument_group(
|
|
15
|
-
f"{constants.ANSI_BOLD}VIDEO PROCESS OPTIONS{constants.ANSI_RESET_ALL}"
|
|
16
|
-
)
|
|
15
|
+
group = parser.add_argument_group(bold_text("VIDEO PROCESS OPTIONS"))
|
|
17
16
|
group.add_argument(
|
|
18
17
|
"--video_sample_distance",
|
|
19
18
|
help="The minimal distance interval, in meters, for sampling video frames. [default: %(default)s]",
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
|
|
3
3
|
from .. import constants
|
|
4
|
+
from ..authenticate import fetch_user_items
|
|
4
5
|
from ..upload import upload
|
|
6
|
+
from .process import bold_text
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
class Command:
|
|
8
10
|
name = "upload"
|
|
9
|
-
help = "
|
|
11
|
+
help = "Upload processed data to Mapillary"
|
|
10
12
|
|
|
11
13
|
@staticmethod
|
|
12
14
|
def add_common_upload_options(group):
|
|
13
15
|
group.add_argument(
|
|
14
16
|
"--user_name",
|
|
15
|
-
help="The Mapillary user account to upload to.
|
|
17
|
+
help="The Mapillary user account to upload to.",
|
|
16
18
|
required=False,
|
|
17
19
|
)
|
|
18
20
|
group.add_argument(
|
|
@@ -21,30 +23,59 @@ class Command:
|
|
|
21
23
|
default=None,
|
|
22
24
|
required=False,
|
|
23
25
|
)
|
|
26
|
+
group.add_argument(
|
|
27
|
+
"--reupload",
|
|
28
|
+
help="Re-upload data that has already been uploaded.",
|
|
29
|
+
action="store_true",
|
|
30
|
+
default=False,
|
|
31
|
+
required=False,
|
|
32
|
+
)
|
|
24
33
|
group.add_argument(
|
|
25
34
|
"--dry_run",
|
|
26
|
-
|
|
35
|
+
"--dryrun",
|
|
36
|
+
help="[DEVELOPMENT] Simulate upload by sending data to a local directory instead of Mapillary servers. Uses a temporary directory by default unless specified by MAPILLARY_UPLOAD_ENDPOINT environment variable.",
|
|
37
|
+
action="store_true",
|
|
38
|
+
default=False,
|
|
39
|
+
required=False,
|
|
40
|
+
)
|
|
41
|
+
group.add_argument(
|
|
42
|
+
"--nofinish",
|
|
43
|
+
help="[DEVELOPMENT] Upload data without finalizing. The data will NOT be stored permanently or appear on the Mapillary website.",
|
|
44
|
+
action="store_true",
|
|
45
|
+
default=False,
|
|
46
|
+
required=False,
|
|
47
|
+
)
|
|
48
|
+
group.add_argument(
|
|
49
|
+
"--noresume",
|
|
50
|
+
help="[DEVELOPMENT] Start upload from the beginning, ignoring any previously interrupted upload sessions.",
|
|
27
51
|
action="store_true",
|
|
28
52
|
default=False,
|
|
29
53
|
required=False,
|
|
30
54
|
)
|
|
31
55
|
|
|
32
56
|
def add_basic_arguments(self, parser):
|
|
33
|
-
group = parser.add_argument_group(
|
|
34
|
-
f"{constants.ANSI_BOLD}UPLOAD OPTIONS{constants.ANSI_RESET_ALL}"
|
|
35
|
-
)
|
|
57
|
+
group = parser.add_argument_group(bold_text("UPLOAD OPTIONS"))
|
|
36
58
|
group.add_argument(
|
|
37
59
|
"--desc_path",
|
|
38
|
-
help=f'Path to the description file
|
|
60
|
+
help=f'Path to the description file with processed image and video metadata (from process command). Use "-" for STDIN. [default: {{IMPORT_PATH}}/{constants.IMAGE_DESCRIPTION_FILENAME}]',
|
|
39
61
|
default=None,
|
|
40
62
|
required=False,
|
|
41
63
|
)
|
|
42
64
|
Command.add_common_upload_options(group)
|
|
43
65
|
|
|
44
66
|
def run(self, vars_args: dict):
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
67
|
+
if "user_items" not in vars_args:
|
|
68
|
+
user_items_args = {
|
|
69
|
+
k: v
|
|
70
|
+
for k, v in vars_args.items()
|
|
71
|
+
if k in inspect.getfullargspec(fetch_user_items).args
|
|
72
|
+
}
|
|
73
|
+
vars_args["user_items"] = fetch_user_items(**user_items_args)
|
|
74
|
+
|
|
75
|
+
upload(
|
|
76
|
+
**{
|
|
77
|
+
k: v
|
|
78
|
+
for k, v in vars_args.items()
|
|
79
|
+
if k in inspect.getfullargspec(upload).args
|
|
80
|
+
}
|
|
81
|
+
)
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
|
|
3
|
+
from ..authenticate import fetch_user_items
|
|
4
|
+
|
|
1
5
|
from .upload import Command as UploadCommand
|
|
2
6
|
from .video_process import Command as VideoProcessCommand
|
|
3
7
|
|
|
@@ -10,10 +14,20 @@ class Command:
|
|
|
10
14
|
VideoProcessCommand().add_basic_arguments(parser)
|
|
11
15
|
UploadCommand().add_basic_arguments(parser)
|
|
12
16
|
|
|
13
|
-
def run(self,
|
|
14
|
-
if
|
|
17
|
+
def run(self, vars_args: dict):
|
|
18
|
+
if vars_args.get("desc_path") is None:
|
|
15
19
|
# \x00 is a special path similiar to /dev/null
|
|
16
20
|
# it tells process command do not write anything
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
vars_args["desc_path"] = "\x00"
|
|
22
|
+
|
|
23
|
+
if "user_items" not in vars_args:
|
|
24
|
+
vars_args["user_items"] = fetch_user_items(
|
|
25
|
+
**{
|
|
26
|
+
k: v
|
|
27
|
+
for k, v in vars_args.items()
|
|
28
|
+
if k in inspect.getfullargspec(fetch_user_items).args
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
VideoProcessCommand().run(vars_args)
|
|
33
|
+
UploadCommand().run(vars_args)
|
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,38 +59,52 @@ def _load_config(config_path: str) -> configparser.ConfigParser:
|
|
|
34
59
|
return config
|
|
35
60
|
|
|
36
61
|
|
|
37
|
-
def load_user(
|
|
38
|
-
user_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
|
-
if not config.has_section(
|
|
66
|
+
if not config.has_section(profile_name):
|
|
44
67
|
return None
|
|
45
|
-
user_items = dict(config.items(
|
|
46
|
-
return T.cast(
|
|
68
|
+
user_items = dict(config.items(profile_name))
|
|
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)
|
|
53
|
-
users =
|
|
54
|
-
load_user(
|
|
55
|
-
|
|
56
|
-
|
|
76
|
+
users = {
|
|
77
|
+
profile_name: load_user(profile_name, config_path=config_path)
|
|
78
|
+
for profile_name in cp.sections()
|
|
79
|
+
}
|
|
80
|
+
return {profile: item for profile, item in users.items() if item is not None}
|
|
57
81
|
|
|
58
82
|
|
|
59
83
|
def update_config(
|
|
60
|
-
|
|
84
|
+
profile_name: str, user_items: UserItem, config_path: str | None = None
|
|
61
85
|
) -> None:
|
|
62
86
|
if config_path is None:
|
|
63
87
|
config_path = MAPILLARY_CONFIG_PATH
|
|
64
88
|
config = _load_config(config_path)
|
|
65
|
-
if not config.has_section(
|
|
66
|
-
config.add_section(
|
|
89
|
+
if not config.has_section(profile_name):
|
|
90
|
+
config.add_section(profile_name)
|
|
67
91
|
for key, val in user_items.items():
|
|
68
|
-
config.set(
|
|
92
|
+
config.set(profile_name, key, T.cast(str, val))
|
|
93
|
+
os.makedirs(os.path.dirname(os.path.abspath(config_path)), exist_ok=True)
|
|
94
|
+
with open(config_path, "w") as fp:
|
|
95
|
+
config.write(fp)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def remove_config(profile_name: str, config_path: str | None = None) -> None:
|
|
99
|
+
if config_path is None:
|
|
100
|
+
config_path = MAPILLARY_CONFIG_PATH
|
|
101
|
+
|
|
102
|
+
config = _load_config(config_path)
|
|
103
|
+
if not config.has_section(profile_name):
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
config.remove_section(profile_name)
|
|
107
|
+
|
|
69
108
|
os.makedirs(os.path.dirname(os.path.abspath(config_path)), exist_ok=True)
|
|
70
109
|
with open(config_path, "w") as fp:
|
|
71
110
|
config.write(fp)
|