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.
Files changed (83) hide show
  1. mapillary_tools/__init__.py +1 -1
  2. mapillary_tools/api_v4.py +237 -16
  3. mapillary_tools/authenticate.py +325 -64
  4. mapillary_tools/{geotag/blackvue_parser.py → blackvue_parser.py} +74 -54
  5. mapillary_tools/camm/camm_builder.py +55 -97
  6. mapillary_tools/camm/camm_parser.py +429 -181
  7. mapillary_tools/commands/__main__.py +12 -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 +19 -5
  11. mapillary_tools/commands/sample_video.py +2 -3
  12. mapillary_tools/commands/upload.py +18 -9
  13. mapillary_tools/commands/video_process_and_upload.py +19 -5
  14. mapillary_tools/config.py +31 -13
  15. mapillary_tools/constants.py +47 -6
  16. mapillary_tools/exceptions.py +34 -35
  17. mapillary_tools/exif_read.py +221 -116
  18. mapillary_tools/exif_write.py +7 -7
  19. mapillary_tools/exiftool_read.py +33 -42
  20. mapillary_tools/exiftool_read_video.py +46 -33
  21. mapillary_tools/exiftool_runner.py +77 -0
  22. mapillary_tools/ffmpeg.py +24 -23
  23. mapillary_tools/geo.py +144 -120
  24. mapillary_tools/geotag/base.py +147 -0
  25. mapillary_tools/geotag/factory.py +291 -0
  26. mapillary_tools/geotag/geotag_images_from_exif.py +14 -131
  27. mapillary_tools/geotag/geotag_images_from_exiftool.py +126 -82
  28. mapillary_tools/geotag/geotag_images_from_gpx.py +53 -118
  29. mapillary_tools/geotag/geotag_images_from_gpx_file.py +13 -126
  30. mapillary_tools/geotag/geotag_images_from_nmea_file.py +4 -5
  31. mapillary_tools/geotag/geotag_images_from_video.py +53 -51
  32. mapillary_tools/geotag/geotag_videos_from_exiftool.py +97 -0
  33. mapillary_tools/geotag/geotag_videos_from_gpx.py +39 -0
  34. mapillary_tools/geotag/geotag_videos_from_video.py +20 -185
  35. mapillary_tools/geotag/image_extractors/base.py +18 -0
  36. mapillary_tools/geotag/image_extractors/exif.py +60 -0
  37. mapillary_tools/geotag/image_extractors/exiftool.py +18 -0
  38. mapillary_tools/geotag/options.py +160 -0
  39. mapillary_tools/geotag/utils.py +52 -16
  40. mapillary_tools/geotag/video_extractors/base.py +18 -0
  41. mapillary_tools/geotag/video_extractors/exiftool.py +70 -0
  42. mapillary_tools/{video_data_extraction/extractors/gpx_parser.py → geotag/video_extractors/gpx.py} +57 -39
  43. mapillary_tools/geotag/video_extractors/native.py +157 -0
  44. mapillary_tools/{geotag → gpmf}/gpmf_parser.py +205 -182
  45. mapillary_tools/{geotag → gpmf}/gps_filter.py +5 -3
  46. mapillary_tools/history.py +7 -13
  47. mapillary_tools/mp4/construct_mp4_parser.py +9 -8
  48. mapillary_tools/mp4/io_utils.py +0 -1
  49. mapillary_tools/mp4/mp4_sample_parser.py +36 -28
  50. mapillary_tools/mp4/simple_mp4_builder.py +10 -9
  51. mapillary_tools/mp4/simple_mp4_parser.py +13 -22
  52. mapillary_tools/process_geotag_properties.py +155 -392
  53. mapillary_tools/process_sequence_properties.py +562 -208
  54. mapillary_tools/sample_video.py +13 -20
  55. mapillary_tools/telemetry.py +26 -13
  56. mapillary_tools/types.py +111 -58
  57. mapillary_tools/upload.py +316 -298
  58. mapillary_tools/upload_api_v4.py +55 -122
  59. mapillary_tools/uploader.py +396 -254
  60. mapillary_tools/utils.py +42 -18
  61. {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0a2.dist-info}/METADATA +3 -2
  62. mapillary_tools-0.14.0a2.dist-info/RECORD +72 -0
  63. {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0a2.dist-info}/WHEEL +1 -1
  64. mapillary_tools/geotag/__init__.py +0 -1
  65. mapillary_tools/geotag/geotag_from_generic.py +0 -22
  66. mapillary_tools/geotag/geotag_images_from_exiftool_both_image_and_video.py +0 -93
  67. mapillary_tools/geotag/geotag_videos_from_exiftool_video.py +0 -145
  68. mapillary_tools/video_data_extraction/cli_options.py +0 -22
  69. mapillary_tools/video_data_extraction/extract_video_data.py +0 -176
  70. mapillary_tools/video_data_extraction/extractors/base_parser.py +0 -75
  71. mapillary_tools/video_data_extraction/extractors/blackvue_parser.py +0 -34
  72. mapillary_tools/video_data_extraction/extractors/camm_parser.py +0 -38
  73. mapillary_tools/video_data_extraction/extractors/exiftool_runtime_parser.py +0 -71
  74. mapillary_tools/video_data_extraction/extractors/exiftool_xml_parser.py +0 -53
  75. mapillary_tools/video_data_extraction/extractors/generic_video_parser.py +0 -52
  76. mapillary_tools/video_data_extraction/extractors/gopro_parser.py +0 -43
  77. mapillary_tools/video_data_extraction/extractors/nmea_parser.py +0 -24
  78. mapillary_tools/video_data_extraction/video_data_parser_factory.py +0 -39
  79. mapillary_tools-0.13.3a1.dist-info/RECORD +0 -75
  80. /mapillary_tools/{geotag → gpmf}/gpmf_gps_filter.py +0 -0
  81. {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0a2.dist-info}/entry_points.txt +0 -0
  82. {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0a2.dist-info/licenses}/LICENSE +0 -0
  83. {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
- from .. import constants, exceptions, VERSION
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: T.Dict) -> None:
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 exceptions.MapillaryUserError as exc:
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", exc.__class__.__name__, exc, exc_info=log_level == logging.DEBUG
171
+ "%s: %s", ex.__class__.__name__, ex, exc_info=log_level == logging.DEBUG
166
172
  )
167
- sys.exit(exc.exit_code)
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 name", default=None, required=False
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
- FileType,
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
- geotag_sources: T.List[GeotagSource] = [
22
- "blackvue_videos",
23
- "camm",
24
- "exif",
25
- "exiftool",
26
- "gopro_videos",
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=",".join(sorted(t.value for t in FileType)),
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
- f"{constants.ANSI_BOLD}PROCESS METADATA OPTIONS{constants.ANSI_RESET_ALL}"
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
- f"{constants.ANSI_BOLD}PROCESS GEOTAGGING OPTIONS{constants.ANSI_RESET_ALL}"
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: %(default)s]",
122
- action="store",
123
- choices=geotag_sources,
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
- f"{constants.ANSI_BOLD}PROCESS SEQUENCE OPTIONS{constants.ANSI_RESET_ALL}"
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, args: dict):
14
- if args.get("desc_path") is None:
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
- args["desc_path"] = "\x00"
18
- ProcessCommand().run(args)
19
- UploadCommand().run(args)
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
- args = {
46
- k: v
47
- for k, v in vars_args.items()
48
- if k in inspect.getfullargspec(upload).args
49
- }
50
- upload(**args)
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, args: dict):
14
- if args.get("desc_path") is None:
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
- args["desc_path"] = "\x00"
18
- VideoProcessCommand().run(args)
19
- UploadCommand().run(args)
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
- user_name: str, config_path: T.Optional[str] = None
39
- ) -> T.Optional[types.UserItem]:
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(user_name):
45
+ if not config.has_section(profile_name):
44
46
  return None
45
- user_items = dict(config.items(user_name))
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: T.Optional[str] = None) -> T.List[types.UserItem]:
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(user_name, config_path=config_path) for user_name in cp.sections()
55
- ]
56
- return [item for item in users if item is not None]
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
- user_name: str, user_items: types.UserItem, config_path: T.Optional[str] = None
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(user_name):
66
- config.add_section(user_name)
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(user_name, key, T.cast(str, val))
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)
@@ -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
- ANSI_BOLD = "\033[1m"
9
- ANSI_RESET_ALL = "\033[0m"
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
- EXIFTOOL_PATH: str = os.getenv(_ENV_PREFIX + "EXIFTOOL_PATH", "exiftool")
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
- UPLOAD_CHUNK_SIZE_MB = float(os.getenv(_ENV_PREFIX + "UPLOAD_CHUNK_SIZE_MB", 16))
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: T.Set[int] = set(
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
+ )
@@ -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(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 MapillaryGPXEmptyError(MapillaryDescriptionError):
55
+ pass
51
56
 
52
57
 
53
- class MapillaryVideoGPSNotFoundError(MapillaryDescriptionError, MapillaryUserError):
54
- exit_code = 9
58
+ class MapillaryGPSNoiseError(MapillaryDescriptionError):
59
+ pass
55
60
 
56
61
 
57
- class MapillaryGPSNoiseError(MapillaryDescriptionError):
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: T.Optional[float],
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 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
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 MapillaryEXIFNotFoundError(MapillaryDescriptionError):
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, MapillaryDescriptionError):
118
+ class MapillaryMetadataValidationError(MapillaryUserError):
120
119
  exit_code = 15