mapillary-tools 0.13.3a1__py3-none-any.whl → 0.14.0a2__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 +237 -16
- 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 +429 -181
- mapillary_tools/commands/__main__.py +12 -6
- 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 +31 -13
- mapillary_tools/constants.py +47 -6
- mapillary_tools/exceptions.py +34 -35
- mapillary_tools/exif_read.py +221 -116
- mapillary_tools/exif_write.py +7 -7
- mapillary_tools/exiftool_read.py +33 -42
- mapillary_tools/exiftool_read_video.py +46 -33
- mapillary_tools/exiftool_runner.py +77 -0
- mapillary_tools/ffmpeg.py +24 -23
- mapillary_tools/geo.py +144 -120
- mapillary_tools/geotag/base.py +147 -0
- mapillary_tools/geotag/factory.py +291 -0
- mapillary_tools/geotag/geotag_images_from_exif.py +14 -131
- mapillary_tools/geotag/geotag_images_from_exiftool.py +126 -82
- mapillary_tools/geotag/geotag_images_from_gpx.py +53 -118
- 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 +53 -51
- mapillary_tools/geotag/geotag_videos_from_exiftool.py +97 -0
- mapillary_tools/geotag/geotag_videos_from_gpx.py +39 -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 +160 -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/{video_data_extraction/extractors/gpx_parser.py → geotag/video_extractors/gpx.py} +57 -39
- mapillary_tools/geotag/video_extractors/native.py +157 -0
- mapillary_tools/{geotag → gpmf}/gpmf_parser.py +205 -182
- mapillary_tools/{geotag → gpmf}/gps_filter.py +5 -3
- mapillary_tools/history.py +7 -13
- mapillary_tools/mp4/construct_mp4_parser.py +9 -8
- 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 +155 -392
- mapillary_tools/process_sequence_properties.py +562 -208
- mapillary_tools/sample_video.py +13 -20
- mapillary_tools/telemetry.py +26 -13
- mapillary_tools/types.py +111 -58
- mapillary_tools/upload.py +316 -298
- mapillary_tools/upload_api_v4.py +55 -122
- mapillary_tools/uploader.py +396 -254
- mapillary_tools/utils.py +42 -18
- {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0a2.dist-info}/METADATA +3 -2
- mapillary_tools-0.14.0a2.dist-info/RECORD +72 -0
- {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0a2.dist-info}/WHEEL +1 -1
- mapillary_tools/geotag/__init__.py +0 -1
- 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/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.0a2.dist-info}/entry_points.txt +0 -0
- {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0a2.dist-info/licenses}/LICENSE +0 -0
- {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0a2.dist-info}/top_level.txt +0 -0
|
@@ -2,10 +2,11 @@ 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
|
|
9
10
|
from . import (
|
|
10
11
|
authenticate,
|
|
11
12
|
process,
|
|
@@ -84,7 +85,7 @@ def configure_logger(logger: logging.Logger, stream=None) -> None:
|
|
|
84
85
|
logger.addHandler(handler)
|
|
85
86
|
|
|
86
87
|
|
|
87
|
-
def _log_params(argvars:
|
|
88
|
+
def _log_params(argvars: dict) -> None:
|
|
88
89
|
MAX_ENTRIES = 5
|
|
89
90
|
|
|
90
91
|
def _stringify(x) -> str:
|
|
@@ -160,11 +161,16 @@ def main():
|
|
|
160
161
|
|
|
161
162
|
try:
|
|
162
163
|
args.func(argvars)
|
|
163
|
-
except
|
|
164
|
+
except requests.HTTPError as ex:
|
|
165
|
+
LOG.error("%s: %s", ex.__class__.__name__, api_v4.readable_http_error(ex))
|
|
166
|
+
# TODO: standardize exit codes as exceptions.MapillaryUserError
|
|
167
|
+
sys.exit(16)
|
|
168
|
+
|
|
169
|
+
except exceptions.MapillaryUserError as ex:
|
|
164
170
|
LOG.error(
|
|
165
|
-
"%s: %s",
|
|
171
|
+
"%s: %s", ex.__class__.__name__, ex, exc_info=log_level == logging.DEBUG
|
|
166
172
|
)
|
|
167
|
-
sys.exit(
|
|
173
|
+
sys.exit(ex.exit_code)
|
|
168
174
|
|
|
169
175
|
|
|
170
176
|
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,7 +1,9 @@
|
|
|
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:
|
|
@@ -30,9 +32,7 @@ class Command:
|
|
|
30
32
|
)
|
|
31
33
|
|
|
32
34
|
def add_basic_arguments(self, parser):
|
|
33
|
-
group = parser.add_argument_group(
|
|
34
|
-
f"{constants.ANSI_BOLD}UPLOAD OPTIONS{constants.ANSI_RESET_ALL}"
|
|
35
|
-
)
|
|
35
|
+
group = parser.add_argument_group(bold_text("UPLOAD OPTIONS"))
|
|
36
36
|
group.add_argument(
|
|
37
37
|
"--desc_path",
|
|
38
38
|
help=f'Path to the description file generated by the process command. The hyphen "-" indicates STDIN. [default: {{IMPORT_PATH}}/{constants.IMAGE_DESCRIPTION_FILENAME}]',
|
|
@@ -42,9 +42,18 @@ class Command:
|
|
|
42
42
|
Command.add_common_upload_options(group)
|
|
43
43
|
|
|
44
44
|
def run(self, vars_args: dict):
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
if "user_items" not in vars_args:
|
|
46
|
+
user_items_args = {
|
|
47
|
+
k: v
|
|
48
|
+
for k, v in vars_args.items()
|
|
49
|
+
if k in inspect.getfullargspec(fetch_user_items).args
|
|
50
|
+
}
|
|
51
|
+
vars_args["user_items"] = fetch_user_items(**user_items_args)
|
|
52
|
+
|
|
53
|
+
upload(
|
|
54
|
+
**{
|
|
55
|
+
k: v
|
|
56
|
+
for k, v in vars_args.items()
|
|
57
|
+
if k in inspect.getfullargspec(upload).args
|
|
58
|
+
}
|
|
59
|
+
)
|
|
@@ -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,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import configparser
|
|
2
4
|
import os
|
|
3
5
|
import typing as T
|
|
@@ -35,37 +37,53 @@ def _load_config(config_path: str) -> configparser.ConfigParser:
|
|
|
35
37
|
|
|
36
38
|
|
|
37
39
|
def load_user(
|
|
38
|
-
|
|
39
|
-
) ->
|
|
40
|
+
profile_name: str, config_path: str | None = None
|
|
41
|
+
) -> types.UserItem | None:
|
|
40
42
|
if config_path is None:
|
|
41
43
|
config_path = MAPILLARY_CONFIG_PATH
|
|
42
44
|
config = _load_config(config_path)
|
|
43
|
-
if not config.has_section(
|
|
45
|
+
if not config.has_section(profile_name):
|
|
44
46
|
return None
|
|
45
|
-
user_items = dict(config.items(
|
|
47
|
+
user_items = dict(config.items(profile_name))
|
|
46
48
|
return T.cast(types.UserItem, user_items)
|
|
47
49
|
|
|
48
50
|
|
|
49
|
-
def list_all_users(config_path:
|
|
51
|
+
def list_all_users(config_path: str | None = None) -> dict[str, types.UserItem]:
|
|
50
52
|
if config_path is None:
|
|
51
53
|
config_path = MAPILLARY_CONFIG_PATH
|
|
52
54
|
cp = _load_config(config_path)
|
|
53
|
-
users =
|
|
54
|
-
load_user(
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
users = {
|
|
56
|
+
profile_name: load_user(profile_name, config_path=config_path)
|
|
57
|
+
for profile_name in cp.sections()
|
|
58
|
+
}
|
|
59
|
+
return {profile: item for profile, item in users.items() if item is not None}
|
|
57
60
|
|
|
58
61
|
|
|
59
62
|
def update_config(
|
|
60
|
-
|
|
63
|
+
profile_name: str, user_items: types.UserItem, config_path: str | None = None
|
|
61
64
|
) -> None:
|
|
62
65
|
if config_path is None:
|
|
63
66
|
config_path = MAPILLARY_CONFIG_PATH
|
|
64
67
|
config = _load_config(config_path)
|
|
65
|
-
if not config.has_section(
|
|
66
|
-
config.add_section(
|
|
68
|
+
if not config.has_section(profile_name):
|
|
69
|
+
config.add_section(profile_name)
|
|
67
70
|
for key, val in user_items.items():
|
|
68
|
-
config.set(
|
|
71
|
+
config.set(profile_name, key, T.cast(str, val))
|
|
72
|
+
os.makedirs(os.path.dirname(os.path.abspath(config_path)), exist_ok=True)
|
|
73
|
+
with open(config_path, "w") as fp:
|
|
74
|
+
config.write(fp)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def remove_config(profile_name: str, config_path: str | None = None) -> None:
|
|
78
|
+
if config_path is None:
|
|
79
|
+
config_path = MAPILLARY_CONFIG_PATH
|
|
80
|
+
|
|
81
|
+
config = _load_config(config_path)
|
|
82
|
+
if not config.has_section(profile_name):
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
config.remove_section(profile_name)
|
|
86
|
+
|
|
69
87
|
os.makedirs(os.path.dirname(os.path.abspath(config_path)), exist_ok=True)
|
|
70
88
|
with open(config_path, "w") as fp:
|
|
71
89
|
config.write(fp)
|
mapillary_tools/constants.py
CHANGED
|
@@ -1,16 +1,29 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import os
|
|
2
|
-
import typing as T
|
|
3
4
|
|
|
4
5
|
import appdirs
|
|
5
6
|
|
|
6
7
|
_ENV_PREFIX = "MAPILLARY_TOOLS_"
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
def _yes_or_no(val: str) -> bool:
|
|
11
|
+
return val.strip().upper() in [
|
|
12
|
+
"1",
|
|
13
|
+
"TRUE",
|
|
14
|
+
"YES",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# In meters
|
|
10
19
|
CUTOFF_DISTANCE = float(os.getenv(_ENV_PREFIX + "CUTOFF_DISTANCE", 600))
|
|
20
|
+
# In seconds
|
|
11
21
|
CUTOFF_TIME = float(os.getenv(_ENV_PREFIX + "CUTOFF_TIME", 60))
|
|
12
22
|
DUPLICATE_DISTANCE = float(os.getenv(_ENV_PREFIX + "DUPLICATE_DISTANCE", 0.1))
|
|
13
23
|
DUPLICATE_ANGLE = float(os.getenv(_ENV_PREFIX + "DUPLICATE_ANGLE", 5))
|
|
24
|
+
MAX_AVG_SPEED = float(
|
|
25
|
+
os.getenv(_ENV_PREFIX + "MAX_AVG_SPEED", 400_000 / 3600)
|
|
26
|
+
) # 400 KM/h
|
|
14
27
|
# in seconds
|
|
15
28
|
VIDEO_SAMPLE_INTERVAL = float(os.getenv(_ENV_PREFIX + "VIDEO_SAMPLE_INTERVAL", -1))
|
|
16
29
|
# in meters
|
|
@@ -18,7 +31,8 @@ VIDEO_SAMPLE_DISTANCE = float(os.getenv(_ENV_PREFIX + "VIDEO_SAMPLE_DISTANCE", 3
|
|
|
18
31
|
VIDEO_DURATION_RATIO = float(os.getenv(_ENV_PREFIX + "VIDEO_DURATION_RATIO", 1))
|
|
19
32
|
FFPROBE_PATH: str = os.getenv(_ENV_PREFIX + "FFPROBE_PATH", "ffprobe")
|
|
20
33
|
FFMPEG_PATH: str = os.getenv(_ENV_PREFIX + "FFMPEG_PATH", "ffmpeg")
|
|
21
|
-
|
|
34
|
+
# When not set, MT will try to check both "exiftool" and "exiftool.exe" from $PATH
|
|
35
|
+
EXIFTOOL_PATH: str | None = os.getenv(_ENV_PREFIX + "EXIFTOOL_PATH")
|
|
22
36
|
IMAGE_DESCRIPTION_FILENAME = os.getenv(
|
|
23
37
|
_ENV_PREFIX + "IMAGE_DESCRIPTION_FILENAME", "mapillary_image_description.json"
|
|
24
38
|
)
|
|
@@ -26,14 +40,18 @@ SAMPLED_VIDEO_FRAMES_FILENAME = os.getenv(
|
|
|
26
40
|
_ENV_PREFIX + "SAMPLED_VIDEO_FRAMES_FILENAME", "mapillary_sampled_video_frames"
|
|
27
41
|
)
|
|
28
42
|
USER_DATA_DIR = appdirs.user_data_dir(appname="mapillary_tools", appauthor="Mapillary")
|
|
29
|
-
|
|
43
|
+
# The chunk size in MB (see chunked transfer encoding https://en.wikipedia.org/wiki/Chunked_transfer_encoding)
|
|
44
|
+
# for uploading data to MLY upload service.
|
|
45
|
+
# Changing this size does not change the number of requests nor affect upload performance,
|
|
46
|
+
# but it affects the responsiveness of the upload progress bar
|
|
47
|
+
UPLOAD_CHUNK_SIZE_MB = float(os.getenv(_ENV_PREFIX + "UPLOAD_CHUNK_SIZE_MB", 1))
|
|
30
48
|
|
|
31
49
|
# DoP value, the lower the better
|
|
32
50
|
# See https://github.com/gopro/gpmf-parser#hero5-black-with-gps-enabled-adds
|
|
33
51
|
# It is used to filter out noisy points
|
|
34
52
|
GOPRO_MAX_DOP100 = int(os.getenv(_ENV_PREFIX + "GOPRO_MAX_DOP100", 1000))
|
|
35
53
|
# Within the GPS stream: 0 - no lock, 2 or 3 - 2D or 3D Lock
|
|
36
|
-
GOPRO_GPS_FIXES:
|
|
54
|
+
GOPRO_GPS_FIXES: set[int] = set(
|
|
37
55
|
int(fix) for fix in os.getenv(_ENV_PREFIX + "GOPRO_GPS_FIXES", "2,3").split(",")
|
|
38
56
|
)
|
|
39
57
|
MAX_UPLOAD_RETRIES: int = int(os.getenv(_ENV_PREFIX + "MAX_UPLOAD_RETRIES", 200))
|
|
@@ -48,3 +66,26 @@ MAX_SEQUENCE_LENGTH = int(os.getenv(_ENV_PREFIX + "MAX_SEQUENCE_LENGTH", 1000))
|
|
|
48
66
|
MAX_SEQUENCE_FILESIZE: str = os.getenv(_ENV_PREFIX + "MAX_SEQUENCE_FILESIZE", "110G")
|
|
49
67
|
# Max number of pixels per sequence (sum of image pixels in the sequence)
|
|
50
68
|
MAX_SEQUENCE_PIXELS: str = os.getenv(_ENV_PREFIX + "MAX_SEQUENCE_PIXELS", "6G")
|
|
69
|
+
|
|
70
|
+
PROMPT_DISABLED: bool = _yes_or_no(os.getenv(_ENV_PREFIX + "PROMPT_DISABLED", "NO"))
|
|
71
|
+
|
|
72
|
+
_AUTH_VERIFICATION_DISABLED: bool = _yes_or_no(
|
|
73
|
+
os.getenv(_ENV_PREFIX + "_AUTH_VERIFICATION_DISABLED", "NO")
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
MAPILLARY_DISABLE_API_LOGGING: bool = _yes_or_no(
|
|
77
|
+
os.getenv("MAPILLARY_DISABLE_API_LOGGING", "NO")
|
|
78
|
+
)
|
|
79
|
+
MAPILLARY__ENABLE_UPLOAD_HISTORY_FOR_DRY_RUN: bool = _yes_or_no(
|
|
80
|
+
os.getenv("MAPILLARY__ENABLE_UPLOAD_HISTORY_FOR_DRY_RUN", "NO")
|
|
81
|
+
)
|
|
82
|
+
MAPILLARY__EXPERIMENTAL_ENABLE_IMU: bool = _yes_or_no(
|
|
83
|
+
os.getenv("MAPILLARY__EXPERIMENTAL_ENABLE_IMU", "NO")
|
|
84
|
+
)
|
|
85
|
+
MAPILLARY_UPLOAD_HISTORY_PATH: str = os.getenv(
|
|
86
|
+
"MAPILLARY_UPLOAD_HISTORY_PATH",
|
|
87
|
+
os.path.join(
|
|
88
|
+
USER_DATA_DIR,
|
|
89
|
+
"upload_history",
|
|
90
|
+
),
|
|
91
|
+
)
|
mapillary_tools/exceptions.py
CHANGED
|
@@ -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,44 +31,35 @@ 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
|
|
46
|
+
class MapillaryGeoTaggingError(MapillaryDescriptionError):
|
|
42
47
|
pass
|
|
43
48
|
|
|
44
49
|
|
|
45
|
-
class
|
|
50
|
+
class MapillaryVideoGPSNotFoundError(MapillaryDescriptionError):
|
|
46
51
|
pass
|
|
47
52
|
|
|
48
53
|
|
|
49
|
-
class MapillaryGPXEmptyError(MapillaryDescriptionError
|
|
50
|
-
|
|
54
|
+
class MapillaryGPXEmptyError(MapillaryDescriptionError):
|
|
55
|
+
pass
|
|
51
56
|
|
|
52
57
|
|
|
53
|
-
class
|
|
54
|
-
|
|
58
|
+
class MapillaryGPSNoiseError(MapillaryDescriptionError):
|
|
59
|
+
pass
|
|
55
60
|
|
|
56
61
|
|
|
57
|
-
class
|
|
62
|
+
class MapillaryStationaryVideoError(MapillaryDescriptionError):
|
|
58
63
|
pass
|
|
59
64
|
|
|
60
65
|
|
|
@@ -68,21 +73,13 @@ class MapillaryOutsideGPXTrackError(MapillaryDescriptionError):
|
|
|
68
73
|
self.gpx_end_time = gpx_end_time
|
|
69
74
|
|
|
70
75
|
|
|
71
|
-
class MapillaryStationaryVideoError(MapillaryDescriptionError, MapillaryUserError):
|
|
72
|
-
exit_code = 10
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
class MapillaryInvalidBlackVueVideoError(MapillaryDescriptionError, MapillaryUserError):
|
|
76
|
-
exit_code = 11
|
|
77
|
-
|
|
78
|
-
|
|
79
76
|
class MapillaryDuplicationError(MapillaryDescriptionError):
|
|
80
77
|
def __init__(
|
|
81
78
|
self,
|
|
82
79
|
message: str,
|
|
83
80
|
desc: T.Mapping[str, T.Any],
|
|
84
81
|
distance: float,
|
|
85
|
-
angle_diff:
|
|
82
|
+
angle_diff: float | None,
|
|
86
83
|
) -> None:
|
|
87
84
|
super().__init__(message)
|
|
88
85
|
self.desc = desc
|
|
@@ -90,17 +87,19 @@ class MapillaryDuplicationError(MapillaryDescriptionError):
|
|
|
90
87
|
self.angle_diff = angle_diff
|
|
91
88
|
|
|
92
89
|
|
|
93
|
-
class
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
90
|
+
class MapillaryExifToolXMLNotFoundError(MapillaryDescriptionError):
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class MapillaryFileTooLargeError(MapillaryDescriptionError):
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class MapillaryCaptureSpeedTooFastError(MapillaryDescriptionError):
|
|
99
|
+
pass
|
|
101
100
|
|
|
102
101
|
|
|
103
|
-
class
|
|
102
|
+
class MapillaryNullIslandError(MapillaryDescriptionError):
|
|
104
103
|
pass
|
|
105
104
|
|
|
106
105
|
|
|
@@ -116,5 +115,5 @@ class MapillaryUploadUnauthorizedError(MapillaryUserError):
|
|
|
116
115
|
exit_code = 14
|
|
117
116
|
|
|
118
117
|
|
|
119
|
-
class MapillaryMetadataValidationError(MapillaryUserError
|
|
118
|
+
class MapillaryMetadataValidationError(MapillaryUserError):
|
|
120
119
|
exit_code = 15
|