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
mapillary_tools/utils.py CHANGED
@@ -1,14 +1,15 @@
1
+ from __future__ import annotations
2
+
1
3
  import hashlib
2
4
  import os
3
5
  import typing as T
6
+ from multiprocessing import Pool
4
7
  from pathlib import Path
5
8
 
6
9
 
7
10
  # Use "hashlib._Hash" instead of hashlib._Hash because:
8
11
  # AttributeError: module 'hashlib' has no attribute '_Hash'
9
- def md5sum_fp(
10
- fp: T.IO[bytes], md5: T.Optional["hashlib._Hash"] = None
11
- ) -> "hashlib._Hash":
12
+ def md5sum_fp(fp: T.IO[bytes], md5: "hashlib._Hash | None" = None) -> "hashlib._Hash":
12
13
  if md5 is None:
13
14
  md5 = hashlib.md5()
14
15
  while True:
@@ -90,12 +91,12 @@ def filter_video_samples(
90
91
 
91
92
 
92
93
  def find_all_image_samples(
93
- image_paths: T.Sequence[Path], video_paths: T.Sequence[Path]
94
- ) -> T.Dict[Path, T.List[Path]]:
94
+ image_paths: T.Iterable[Path], video_paths: T.Iterable[Path]
95
+ ) -> dict[Path, list[Path]]:
95
96
  # TODO: not work with the same filenames, e.g. foo/hello.mp4 and bar/hello.mp4
96
97
  video_basenames = {path.name: path for path in video_paths}
97
98
 
98
- image_samples_by_video_path: T.Dict[Path, T.List[Path]] = {}
99
+ image_samples_by_video_path: dict[Path, list[Path]] = {}
99
100
  for image_path in image_paths:
100
101
  # If you want to walk an arbitrary filesystem path upwards,
101
102
  # it is recommended to first call Path.resolve() so as to resolve symlinks and eliminate “..” components.
@@ -112,7 +113,7 @@ def find_all_image_samples(
112
113
 
113
114
 
114
115
  def deduplicate_paths(paths: T.Iterable[Path]) -> T.Generator[Path, None, None]:
115
- resolved_paths: T.Set[Path] = set()
116
+ resolved_paths: set[Path] = set()
116
117
  for p in paths:
117
118
  resolved = p.resolve()
118
119
  if resolved not in resolved_paths:
@@ -121,10 +122,10 @@ def deduplicate_paths(paths: T.Iterable[Path]) -> T.Generator[Path, None, None]:
121
122
 
122
123
 
123
124
  def find_images(
124
- import_paths: T.Sequence[Path],
125
+ import_paths: T.Iterable[Path],
125
126
  skip_subfolders: bool = False,
126
- ) -> T.List[Path]:
127
- image_paths: T.List[Path] = []
127
+ ) -> list[Path]:
128
+ image_paths: list[Path] = []
128
129
  for path in import_paths:
129
130
  if path.is_dir():
130
131
  image_paths.extend(
@@ -139,10 +140,10 @@ def find_images(
139
140
 
140
141
 
141
142
  def find_videos(
142
- import_paths: T.Sequence[Path],
143
+ import_paths: T.Iterable[Path],
143
144
  skip_subfolders: bool = False,
144
- ) -> T.List[Path]:
145
- video_paths: T.List[Path] = []
145
+ ) -> list[Path]:
146
+ video_paths: list[Path] = []
146
147
  for path in import_paths:
147
148
  if path.is_dir():
148
149
  video_paths.extend(
@@ -157,10 +158,10 @@ def find_videos(
157
158
 
158
159
 
159
160
  def find_zipfiles(
160
- import_paths: T.Sequence[Path],
161
+ import_paths: T.Iterable[Path],
161
162
  skip_subfolders: bool = False,
162
- ) -> T.List[Path]:
163
- zip_paths: T.List[Path] = []
163
+ ) -> list[Path]:
164
+ zip_paths: list[Path] = []
164
165
  for path in import_paths:
165
166
  if path.is_dir():
166
167
  zip_paths.extend(
@@ -174,8 +175,8 @@ def find_zipfiles(
174
175
  return list(deduplicate_paths(zip_paths))
175
176
 
176
177
 
177
- def find_xml_files(import_paths: T.Sequence[Path]) -> T.List[Path]:
178
- xml_paths: T.List[Path] = []
178
+ def find_xml_files(import_paths: T.Iterable[Path]) -> list[Path]:
179
+ xml_paths: list[Path] = []
179
180
  for path in import_paths:
180
181
  if path.is_dir():
181
182
  # XML could be hidden in hidden folders
@@ -194,3 +195,26 @@ def find_xml_files(import_paths: T.Sequence[Path]) -> T.List[Path]:
194
195
 
195
196
  def get_file_size(path: Path) -> int:
196
197
  return os.path.getsize(path)
198
+
199
+
200
+ TMapIn = T.TypeVar("TMapIn")
201
+ TMapOut = T.TypeVar("TMapOut")
202
+
203
+
204
+ def mp_map_maybe(
205
+ func: T.Callable[[TMapIn], TMapOut],
206
+ iterable: T.Iterable[TMapIn],
207
+ num_processes: int | None = None,
208
+ ) -> T.Generator[TMapOut, None, None]:
209
+ if num_processes is None:
210
+ pool_num_processes = None
211
+ disable_multiprocessing = False
212
+ else:
213
+ pool_num_processes = max(num_processes, 1)
214
+ disable_multiprocessing = num_processes <= 0
215
+
216
+ if disable_multiprocessing:
217
+ yield from map(func, iterable)
218
+ else:
219
+ with Pool(processes=pool_num_processes) as pool:
220
+ yield from pool.imap(func, iterable)
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: mapillary_tools
3
- Version: 0.13.3a1
3
+ Version: 0.14.0a2
4
4
  Summary: Mapillary Image/Video Import Pipeline
5
5
  Home-page: https://github.com/mapillary/mapillary_tools
6
6
  Author: Mapillary
@@ -23,6 +23,7 @@ Dynamic: description
23
23
  Dynamic: description-content-type
24
24
  Dynamic: home-page
25
25
  Dynamic: license
26
+ Dynamic: license-file
26
27
  Dynamic: requires-dist
27
28
  Dynamic: requires-python
28
29
  Dynamic: summary
@@ -0,0 +1,72 @@
1
+ mapillary_tools/__init__.py,sha256=AdfWnUWJogoEJm_EqofQxlG8NYL78h0B0M3Igr2xQpE,21
2
+ mapillary_tools/api_v4.py,sha256=uvz48tb2HaM1wfomaUR0V2HdId39HoxnQAc1WWgptvo,10091
3
+ mapillary_tools/authenticate.py,sha256=KG3p_7D4D7nUvrGRLGp56_ogLy7JUHzkGSpBw8V70LY,11091
4
+ mapillary_tools/blackvue_parser.py,sha256=Gmz3M1cvBmB2W3Pme6x3w2yuixDpKiTOkQe82iPXr2s,3613
5
+ mapillary_tools/config.py,sha256=uoNW5PnB1a2TW959KmNSYgJ1Y4_trn0xxPHJQ6T-cbI,2633
6
+ mapillary_tools/constants.py,sha256=BVnAlqz2XJfodNXsJfsGIV0L4P_3--oOgw1pR38D-Ls,3770
7
+ mapillary_tools/exceptions.py,sha256=P8vEh3pfOqyzJgOiQiRAcWsZTrcawjic7i4v3k335tM,2411
8
+ mapillary_tools/exif_read.py,sha256=gagyRv06RkZPRHQhyV-5-uQzxHgxOsVqUX2aC8QmCyM,32145
9
+ mapillary_tools/exif_write.py,sha256=Ue0ngLKCfuLQM7X4h5ETjSInUDcyEBWOR6v8ko7DJ1o,8769
10
+ mapillary_tools/exiftool_read.py,sha256=5uatYE9mgbg2d9NAnPfX22nSRBjXhJ9ayMqNMd4QwGM,15779
11
+ mapillary_tools/exiftool_read_video.py,sha256=WXpw7CI7B4daJHRWzzVXAyVgDrFN8nPxQ3UPcVQqRJM,14760
12
+ mapillary_tools/exiftool_runner.py,sha256=J04V0Y70kTiGE2q2m5TKWfORd0hmYNmAsmrZxb34S3s,2316
13
+ mapillary_tools/ffmpeg.py,sha256=tRgdChGHAhHXjZPivDuVImubrJWfSDZDBZ7mSzkeABc,15698
14
+ mapillary_tools/geo.py,sha256=MSfkjSAgTAeuBqncQ8-QXvDuJ7ar7bTt5P_e9HDIgVs,10604
15
+ mapillary_tools/history.py,sha256=8ng2WHXadzbE5gnsoik6gbmRoEXTuGPp997RBiiWy08,1649
16
+ mapillary_tools/ipc.py,sha256=DwWQb9hNshx0bg0Fo5NjY0mXjs-FkbR6tIQmjMgMtmg,1089
17
+ mapillary_tools/process_geotag_properties.py,sha256=f0Up7yNzgLn32VBFMzoMT6SCsdzFUFYmloOsVHmK5k0,13920
18
+ mapillary_tools/process_sequence_properties.py,sha256=LSYyCH1nmEcYWWZKF1bOYh4hqjwXdz4FY9TqQQfW7AU,23418
19
+ mapillary_tools/sample_video.py,sha256=MwTr2bY5RFnOFThNPg3YiyXCXzWOlTXKq7EOri_W3Vw,13880
20
+ mapillary_tools/telemetry.py,sha256=lL6qQbtOZft4DZZrCNK3njlwHT_30zLyYS_YRN5pgHY,1568
21
+ mapillary_tools/types.py,sha256=0O4fzbwwoT6Znp2Ag1ptjzBKGb3E_RhGyqUUZu6gkPY,23078
22
+ mapillary_tools/upload.py,sha256=exQeEh3jpys94k-3QDT-4xoeJ_lAHbQBoy749hbPfnA,24878
23
+ mapillary_tools/upload_api_v4.py,sha256=dTEpQDnV11TyPf5hefd-FVeEfw7WEVYyZcIUJttUEp0,6417
24
+ mapillary_tools/uploader.py,sha256=xPEszuXlgcgCklGfsgL8qN1ozsFuU4rLT4jocMD2dVI,18694
25
+ mapillary_tools/utils.py,sha256=ft-TrqgwWq1a-HpQqnCWIortZbjjTOywllPi8HBA3FU,6642
26
+ mapillary_tools/camm/camm_builder.py,sha256=ub6Z9ijep8zAo1NOlU51Gxk95kQ2vfN58YgVCLmNMRk,9211
27
+ mapillary_tools/camm/camm_parser.py,sha256=aNHP65hNXYQBWBTfhaj_S5XYzmAHhjwcAfGhbm83__o,18043
28
+ mapillary_tools/commands/__init__.py,sha256=41CFrPLGlG3566uhxssEF3TGAtSpADFPPcDMHbViU0E,171
29
+ mapillary_tools/commands/__main__.py,sha256=5gwngScbvrSNF2S1CA5w_qZgxW_kFevu5NQ89U9u1ZE,5046
30
+ mapillary_tools/commands/authenticate.py,sha256=yqtpHMYzkyBrrchj6MARxB0ywUTfqCEOPkMbkyaO9Ks,1344
31
+ mapillary_tools/commands/process.py,sha256=Japc6_P0B_8HzoM8_82P3_YAiyBBaQZXS9TZF46pbMM,9771
32
+ mapillary_tools/commands/process_and_upload.py,sha256=-RB_86a5xKfQ7Ye79dh6yyJQpZg2xnJZAWOJsUNbUtQ,1041
33
+ mapillary_tools/commands/sample_video.py,sha256=jtnrZrsqqv5eYV1chNTas7RhfbeKBqbAUDUNRFjF01w,3253
34
+ mapillary_tools/commands/upload.py,sha256=YnA1iSvNcFeV7kMs1p4TdaPBUWUFYb7X-vFhQeSz0eI,2005
35
+ mapillary_tools/commands/video_process.py,sha256=-wQeeIwWXPmy81HQHam5A0huMLRHknkEFa_V1OwElU4,890
36
+ mapillary_tools/commands/video_process_and_upload.py,sha256=hOyq9L9TuD0JcqFSOOxdCdgsBA1iJ6fu1TtDbsUr8sI,1088
37
+ mapillary_tools/commands/zip.py,sha256=DVQuMLpbstwiy5o4pU_fBvM6eORuFjLeySd80AhHKU0,991
38
+ mapillary_tools/geotag/base.py,sha256=JdLBXHlC22gqmZkyfAykv1osrzitWoCK8RFHdCd5qlc,4864
39
+ mapillary_tools/geotag/factory.py,sha256=2HIOnUtfxsgWn2GF0PbbgHq3Tzyssrl4T39YDMtN42U,9564
40
+ mapillary_tools/geotag/geotag_images_from_exif.py,sha256=a5fDOp4tlEoVNwrOCJBrvSPzu1LbyxL4RNrNDD0tPP0,608
41
+ mapillary_tools/geotag/geotag_images_from_exiftool.py,sha256=YxMFwv3A35Hh9tcGX1B1Si_m6TNep54SL5XFAQadQh0,5302
42
+ mapillary_tools/geotag/geotag_images_from_gpx.py,sha256=b_4xKVUt0HLIjNN-v7ZVddXDZ9cdxAfecB5Dj2X6XQE,6353
43
+ mapillary_tools/geotag/geotag_images_from_gpx_file.py,sha256=HYQkwak32YBDuRrTNiIZOmE3iImCRc22HWWb485WRS4,1121
44
+ mapillary_tools/geotag/geotag_images_from_nmea_file.py,sha256=J8xj6ch9bMPRubJtsRGeb3sb9LyB0ZYy65NPEOVkUe8,1597
45
+ mapillary_tools/geotag/geotag_images_from_video.py,sha256=5_qVgLt_DKl7oQrr9kWxi4iM143_HPAx5Lau-3fB7t4,3136
46
+ mapillary_tools/geotag/geotag_videos_from_exiftool.py,sha256=7chO_tb7ECsBlOkFhiXQ4fhomVPkfBOWtdfzXGpsJes,3168
47
+ mapillary_tools/geotag/geotag_videos_from_gpx.py,sha256=k4a1imUVLg2yDqggCwTSqjvY7jXQd3E-vniBuJj2d4M,1017
48
+ mapillary_tools/geotag/geotag_videos_from_video.py,sha256=T8XS4lVF2Wz4eDXNi5Vt076M5dxjxJXibVrWhqVvErs,863
49
+ mapillary_tools/geotag/options.py,sha256=7Fz0QoCMyZZbhkKvY4HPNWYfnKdQSWE2oRXb7jYZhmA,4336
50
+ mapillary_tools/geotag/utils.py,sha256=0DenLVCLP-GZdk0rCFP0V1tjMGK4XAvYro3I_1RKgCA,1733
51
+ mapillary_tools/geotag/image_extractors/base.py,sha256=XoNrLCbJtd-MN-snbhv6zyr6zBfJRoJkntV0ptrh6qg,358
52
+ mapillary_tools/geotag/image_extractors/exif.py,sha256=cCUegbFqWxjAP4oOP1nZmwoJISWeKgjGO8h_t7nucHs,2079
53
+ mapillary_tools/geotag/image_extractors/exiftool.py,sha256=zokJmf-D2rPvASRJs3dZzEu7j82igpMOr4SE6Z1nsVg,497
54
+ mapillary_tools/geotag/video_extractors/base.py,sha256=_3B37T-m2HGy77avAF8i_kEatIyQqFfBu49qVnx_0PU,357
55
+ mapillary_tools/geotag/video_extractors/exiftool.py,sha256=CB49qZqgmxECma-N5757aEn37xXRbCNRcT4o-Ip7dCw,2312
56
+ mapillary_tools/geotag/video_extractors/gpx.py,sha256=Oycdn74CNiq1j8dAq7O9YiVNTun2mFWRH9VTUn_viRk,4114
57
+ mapillary_tools/geotag/video_extractors/native.py,sha256=XChOyDvrf88Vu8ED-FTT_hzax2FllokanszSW4Q2MEA,5702
58
+ mapillary_tools/gpmf/gpmf_gps_filter.py,sha256=7cg8wEjC1DrujKY76FZguXsaPqTRkG9-t32OeuOJQIc,2755
59
+ mapillary_tools/gpmf/gpmf_parser.py,sha256=uOVXkwJxC3Y2YfTdzUDpt7Bh0pdVqa5u0WUuv7pEJEs,24670
60
+ mapillary_tools/gpmf/gps_filter.py,sha256=zhXkuvr0Dd1bxGTYBwvk6P7xasY_RLuWjHaX7CdBayc,3796
61
+ mapillary_tools/mp4/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
62
+ mapillary_tools/mp4/construct_mp4_parser.py,sha256=ArJ5C8iWJhHNPFkN048DXh6EwQ8LMfnTMwoNlKV9NoI,17191
63
+ mapillary_tools/mp4/io_utils.py,sha256=KZaJTlgFS27Oh3pcA5MKXYFoCifqgFaEZJyU6lb1jc4,5416
64
+ mapillary_tools/mp4/mp4_sample_parser.py,sha256=0ILTq8M6mXFTI3agKgljpvO9uYa7HXGUGZpdHT8a6ac,11547
65
+ mapillary_tools/mp4/simple_mp4_builder.py,sha256=9TUGk1hzI6mQFN1P30jwHL3dCYz3Zz7rsm8UBvMAqMc,12734
66
+ mapillary_tools/mp4/simple_mp4_parser.py,sha256=g3vvPhBoNu7anhVzC5_XQCV7IwfRWro1vJ6d6GyDkHE,6315
67
+ mapillary_tools-0.14.0a2.dist-info/licenses/LICENSE,sha256=l2D8cKfFmmJq_wcVq_JElPJrlvWQOzNWx7gMLINucxc,1292
68
+ mapillary_tools-0.14.0a2.dist-info/METADATA,sha256=UDVbKn5kqizwZmE70erGYDOnSz8sg9CuTd81GHSWtDI,19782
69
+ mapillary_tools-0.14.0a2.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
70
+ mapillary_tools-0.14.0a2.dist-info/entry_points.txt,sha256=A3f3LP-BO_P-U8Y29QfpT4jx6Mjk3sXjTi2Yew4bvj8,75
71
+ mapillary_tools-0.14.0a2.dist-info/top_level.txt,sha256=FbDkMgOrt1S70ho1WSBrOwzKOSkJFDwwqFOoY5-527s,16
72
+ mapillary_tools-0.14.0a2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.2)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1 +0,0 @@
1
- from .. import geo # noqa: F401
@@ -1,22 +0,0 @@
1
- import abc
2
- import typing as T
3
-
4
- from .. import types
5
-
6
-
7
- class GeotagImagesFromGeneric(abc.ABC):
8
- def __init__(self) -> None:
9
- pass
10
-
11
- @abc.abstractmethod
12
- def to_description(self) -> T.List[types.ImageMetadataOrError]:
13
- raise NotImplementedError
14
-
15
-
16
- class GeotagVideosFromGeneric(abc.ABC):
17
- def __init__(self) -> None:
18
- pass
19
-
20
- @abc.abstractmethod
21
- def to_description(self) -> T.List[types.VideoMetadataOrError]:
22
- raise NotImplementedError
@@ -1,93 +0,0 @@
1
- import logging
2
- import typing as T
3
- from pathlib import Path
4
-
5
- from .. import exiftool_read, types, utils
6
- from . import (
7
- geotag_images_from_exiftool,
8
- geotag_images_from_video,
9
- geotag_videos_from_exiftool_video,
10
- )
11
- from .geotag_from_generic import GeotagImagesFromGeneric
12
-
13
-
14
- LOG = logging.getLogger(__name__)
15
-
16
-
17
- class GeotagImagesFromExifToolBothImageAndVideo(GeotagImagesFromGeneric):
18
- def __init__(
19
- self,
20
- image_paths: T.Sequence[Path],
21
- xml_path: Path,
22
- offset_time: float = 0.0,
23
- num_processes: T.Optional[int] = None,
24
- ):
25
- self.image_paths = image_paths
26
- self.xml_path = xml_path
27
- self.offset_time = offset_time
28
- self.num_processes = num_processes
29
- super().__init__()
30
-
31
- def to_description(self) -> T.List[types.ImageMetadataOrError]:
32
- # will return this list
33
- final_image_metadatas: T.List[types.ImageMetadataOrError] = []
34
-
35
- # find the images that can be geotagged from EXIF
36
- image_metadatas_from_exiftool = (
37
- geotag_images_from_exiftool.GeotagImagesFromExifTool(
38
- self.image_paths,
39
- self.xml_path,
40
- num_processes=self.num_processes,
41
- ).to_description()
42
- )
43
-
44
- # find all video paths in self.xml_path
45
- rdf_description_by_path = exiftool_read.index_rdf_description_by_path(
46
- [self.xml_path]
47
- )
48
- video_paths = utils.find_videos(
49
- [Path(pathstr) for pathstr in rdf_description_by_path.keys()],
50
- skip_subfolders=True,
51
- )
52
-
53
- # will try to geotag these error metadatas from video later
54
- error_metadata_by_image_path = {}
55
- for image_metadata in image_metadatas_from_exiftool:
56
- if isinstance(image_metadata, types.ErrorMetadata):
57
- error_metadata_by_image_path[image_metadata.filename] = image_metadata
58
- else:
59
- final_image_metadatas.append(image_metadata)
60
-
61
- maybe_image_samples = list(error_metadata_by_image_path.keys())
62
-
63
- # find all video paths that have sample images
64
- video_paths_with_image_samples = list(
65
- utils.find_all_image_samples(maybe_image_samples, video_paths).keys()
66
- )
67
-
68
- video_metadatas = (
69
- geotag_videos_from_exiftool_video.GeotagVideosFromExifToolVideo(
70
- video_paths_with_image_samples,
71
- self.xml_path,
72
- num_processes=self.num_processes,
73
- ).to_description()
74
- )
75
-
76
- image_metadatas_from_video = geotag_images_from_video.GeotagImagesFromVideo(
77
- maybe_image_samples,
78
- video_metadatas,
79
- offset_time=self.offset_time,
80
- num_processes=self.num_processes,
81
- ).to_description()
82
- final_image_metadatas.extend(image_metadatas_from_video)
83
-
84
- # add back error metadatas that can not be geotagged at all
85
- actual_image_sample_paths = set(
86
- image_metadata.filename for image_metadata in image_metadatas_from_video
87
- )
88
- for path, error_metadata in error_metadata_by_image_path.items():
89
- if path not in actual_image_sample_paths:
90
- final_image_metadatas.append(error_metadata)
91
-
92
- assert len(final_image_metadatas) <= len(self.image_paths)
93
- return final_image_metadatas
@@ -1,145 +0,0 @@
1
- import logging
2
- import typing as T
3
- import xml.etree.ElementTree as ET
4
- from multiprocessing import Pool
5
- from pathlib import Path
6
-
7
- from tqdm import tqdm
8
-
9
- from .. import exceptions, exiftool_read, geo, types, utils
10
- from ..exiftool_read_video import ExifToolReadVideo
11
- from ..telemetry import GPSPoint
12
- from . import gpmf_gps_filter, utils as video_utils
13
- from .geotag_from_generic import GeotagVideosFromGeneric
14
-
15
- LOG = logging.getLogger(__name__)
16
- _DESCRIPTION_TAG = "rdf:Description"
17
-
18
-
19
- class GeotagVideosFromExifToolVideo(GeotagVideosFromGeneric):
20
- def __init__(
21
- self,
22
- video_paths: T.Sequence[Path],
23
- xml_path: Path,
24
- num_processes: T.Optional[int] = None,
25
- ):
26
- self.video_paths = video_paths
27
- self.xml_path = xml_path
28
- self.num_processes = num_processes
29
- super().__init__()
30
-
31
- @staticmethod
32
- def geotag_video(element: ET.Element) -> types.VideoMetadataOrError:
33
- video_path = exiftool_read.find_rdf_description_path(element)
34
- assert video_path is not None, "must find the path from the element"
35
-
36
- try:
37
- exif = ExifToolReadVideo(ET.ElementTree(element))
38
-
39
- points = exif.extract_gps_track()
40
-
41
- if not points:
42
- raise exceptions.MapillaryVideoGPSNotFoundError(
43
- "No GPS data found from the video"
44
- )
45
-
46
- points = geo.extend_deduplicate_points(points)
47
- assert points, "must have at least one point"
48
-
49
- if all(isinstance(p, GPSPoint) for p in points):
50
- points = T.cast(
51
- T.List[geo.Point],
52
- gpmf_gps_filter.remove_noisy_points(
53
- T.cast(T.List[GPSPoint], points)
54
- ),
55
- )
56
- if not points:
57
- raise exceptions.MapillaryGPSNoiseError("GPS is too noisy")
58
-
59
- stationary = video_utils.is_video_stationary(
60
- geo.get_max_distance_from_start([(p.lat, p.lon) for p in points])
61
- )
62
-
63
- if stationary:
64
- raise exceptions.MapillaryStationaryVideoError("Stationary video")
65
-
66
- video_metadata = types.VideoMetadata(
67
- video_path,
68
- md5sum=None,
69
- filesize=utils.get_file_size(video_path),
70
- filetype=types.FileType.VIDEO,
71
- points=points,
72
- make=exif.extract_make(),
73
- model=exif.extract_model(),
74
- )
75
-
76
- LOG.debug("Calculating MD5 checksum for %s", str(video_metadata.filename))
77
-
78
- video_metadata.update_md5sum()
79
-
80
- except Exception as ex:
81
- if not isinstance(ex, exceptions.MapillaryDescriptionError):
82
- LOG.warning(
83
- "Failed to geotag video %s: %s",
84
- video_path,
85
- str(ex),
86
- exc_info=LOG.getEffectiveLevel() <= logging.DEBUG,
87
- )
88
- return types.describe_error_metadata(
89
- ex, video_path, filetype=types.FileType.VIDEO
90
- )
91
-
92
- return video_metadata
93
-
94
- def to_description(self) -> T.List[types.VideoMetadataOrError]:
95
- rdf_description_by_path = exiftool_read.index_rdf_description_by_path(
96
- [self.xml_path]
97
- )
98
-
99
- error_metadatas: T.List[types.ErrorMetadata] = []
100
- rdf_descriptions: T.List[ET.Element] = []
101
- for path in self.video_paths:
102
- rdf_description = rdf_description_by_path.get(
103
- exiftool_read.canonical_path(path)
104
- )
105
- if rdf_description is None:
106
- exc = exceptions.MapillaryEXIFNotFoundError(
107
- f"The {_DESCRIPTION_TAG} XML element for the video not found"
108
- )
109
- error_metadatas.append(
110
- types.describe_error_metadata(
111
- exc, path, filetype=types.FileType.VIDEO
112
- )
113
- )
114
- else:
115
- rdf_descriptions.append(rdf_description)
116
-
117
- if self.num_processes is None:
118
- num_processes = self.num_processes
119
- disable_multiprocessing = False
120
- else:
121
- num_processes = max(self.num_processes, 1)
122
- disable_multiprocessing = self.num_processes <= 0
123
-
124
- with Pool(processes=num_processes) as pool:
125
- video_metadatas_iter: T.Iterator[types.VideoMetadataOrError]
126
- if disable_multiprocessing:
127
- video_metadatas_iter = map(
128
- GeotagVideosFromExifToolVideo.geotag_video, rdf_descriptions
129
- )
130
- else:
131
- video_metadatas_iter = pool.imap(
132
- GeotagVideosFromExifToolVideo.geotag_video,
133
- rdf_descriptions,
134
- )
135
- video_metadata_or_errors = list(
136
- tqdm(
137
- video_metadatas_iter,
138
- desc="Extracting GPS tracks from ExifTool XML",
139
- unit="videos",
140
- disable=LOG.getEffectiveLevel() <= logging.DEBUG,
141
- total=len(self.video_paths),
142
- )
143
- )
144
-
145
- return error_metadatas + video_metadata_or_errors
@@ -1,22 +0,0 @@
1
- import typing as T
2
- from pathlib import Path
3
-
4
-
5
- known_parser_options = ["source", "pattern", "exiftool_path"]
6
-
7
-
8
- class CliParserOptions(T.TypedDict, total=False):
9
- source: str
10
- pattern: T.Optional[str]
11
- exiftool_path: T.Optional[Path]
12
-
13
-
14
- class CliOptions(T.TypedDict, total=False):
15
- paths: T.Sequence[Path]
16
- recursive: bool
17
- geotag_sources_options: T.Sequence[CliParserOptions]
18
- geotag_source_path: Path
19
- exiftool_path: Path
20
- num_processes: int
21
- device_make: T.Optional[str]
22
- device_model: T.Optional[str]
@@ -1,176 +0,0 @@
1
- import logging
2
- import typing as T
3
- from multiprocessing import Pool
4
- from pathlib import Path
5
-
6
- import tqdm
7
-
8
- from .. import exceptions, geo, utils
9
- from ..geotag import gpmf_gps_filter, utils as video_utils
10
- from ..telemetry import GPSPoint
11
- from ..types import (
12
- ErrorMetadata,
13
- FileType,
14
- MetadataOrError,
15
- VideoMetadata,
16
- VideoMetadataOrError,
17
- )
18
- from . import video_data_parser_factory
19
- from .cli_options import CliOptions
20
- from .extractors.base_parser import BaseParser
21
-
22
-
23
- LOG = logging.getLogger(__name__)
24
-
25
-
26
- class VideoDataExtractor:
27
- options: CliOptions
28
-
29
- def __init__(self, options: CliOptions) -> None:
30
- self.options = options
31
-
32
- def process(self) -> T.List[MetadataOrError]:
33
- paths = self.options["paths"]
34
- self._check_paths(paths)
35
- video_files = utils.find_videos(paths)
36
- self._check_sources_cardinality(video_files)
37
-
38
- num_processes = self.options["num_processes"] or None
39
- with Pool(processes=num_processes) as pool:
40
- if num_processes == 1:
41
- iter: T.Iterator[VideoMetadataOrError] = map(
42
- self.process_file, video_files
43
- )
44
- else:
45
- iter = pool.imap(self.process_file, video_files)
46
-
47
- video_metadata_or_errors = list(
48
- tqdm.tqdm(
49
- iter,
50
- desc="Extracting GPS tracks",
51
- unit="videos",
52
- disable=LOG.getEffectiveLevel() <= logging.DEBUG,
53
- total=len(video_files),
54
- )
55
- )
56
-
57
- return video_metadata_or_errors
58
-
59
- def process_file(self, file: Path) -> VideoMetadataOrError:
60
- parsers = video_data_parser_factory.make_parsers(file, self.options)
61
- points: T.Sequence[geo.Point] = []
62
- make = self.options["device_make"]
63
- model = self.options["device_model"]
64
-
65
- ex: T.Optional[Exception]
66
- for parser in parsers:
67
- log_vars = {
68
- "filename": file,
69
- "parser": parser.parser_label,
70
- "source": parser.geotag_source_path,
71
- }
72
- try:
73
- if not points:
74
- points = self._extract_points(parser, log_vars)
75
- if not model:
76
- model = parser.extract_model()
77
- if not make:
78
- make = parser.extract_make()
79
- except Exception as e:
80
- ex = e
81
- LOG.warning(
82
- '%(filename)s: Exception for parser %(parser)s while processing source %(source)s: "%(e)s"',
83
- {**log_vars, "e": e},
84
- )
85
-
86
- # After trying all parsers, return the points if we found any, otherwise
87
- # the last exception thrown or a default one.
88
- # Note that if we have points, we return them, regardless of exceptions
89
- # with make or model.
90
- if points:
91
- video_metadata = VideoMetadata(
92
- filename=file,
93
- filetype=FileType.VIDEO,
94
- md5sum=None,
95
- filesize=utils.get_file_size(file),
96
- points=points,
97
- make=make,
98
- model=model,
99
- )
100
- video_metadata.update_md5sum()
101
- return video_metadata
102
- else:
103
- return ErrorMetadata(
104
- filename=file,
105
- error=(
106
- ex
107
- if ex
108
- else exceptions.MapillaryVideoGPSNotFoundError(
109
- "No GPS data found from the video"
110
- )
111
- ),
112
- filetype=FileType.VIDEO,
113
- )
114
-
115
- def _extract_points(
116
- self, parser: BaseParser, log_vars: T.Dict
117
- ) -> T.Sequence[geo.Point]:
118
- points = parser.extract_points()
119
- if points:
120
- LOG.debug(
121
- "%(filename)s: %(points)d points extracted by parser %(parser)s from file %(source)s}",
122
- {**log_vars, "points": len(points)},
123
- )
124
-
125
- return self._sanitize_points(points)
126
-
127
- @staticmethod
128
- def _check_paths(import_paths: T.Sequence[Path]):
129
- for path in import_paths:
130
- if not path.is_file() and not path.is_dir():
131
- raise exceptions.MapillaryFileNotFoundError(
132
- f"Import file or directory not found: {path}"
133
- )
134
-
135
- def _check_sources_cardinality(self, files: T.Sequence[Path]):
136
- if len(files) > 1:
137
- for parser_opts in self.options["geotag_sources_options"]:
138
- pattern = parser_opts.get("pattern")
139
- if pattern and "%" not in pattern:
140
- raise exceptions.MapillaryUserError(
141
- "Multiple video files found: Geotag source pattern for source %s must include filename placeholders",
142
- parser_opts["source"],
143
- )
144
-
145
- @staticmethod
146
- def _sanitize_points(points: T.Sequence[geo.Point]) -> T.Sequence[geo.Point]:
147
- """
148
- Deduplicates points, when possible removes noisy ones, and checks
149
- against stationary videos
150
- """
151
-
152
- if not points:
153
- raise exceptions.MapillaryVideoGPSNotFoundError(
154
- "No GPS data found in the given sources"
155
- )
156
-
157
- points = geo.extend_deduplicate_points(points)
158
-
159
- if all(isinstance(p, GPSPoint) for p in points):
160
- points = T.cast(
161
- T.Sequence[geo.Point],
162
- gpmf_gps_filter.remove_noisy_points(
163
- T.cast(T.Sequence[GPSPoint], points)
164
- ),
165
- )
166
- if not points:
167
- raise exceptions.MapillaryGPSNoiseError("GPS is too noisy")
168
-
169
- stationary = video_utils.is_video_stationary(
170
- geo.get_max_distance_from_start([(p.lat, p.lon) for p in points])
171
- )
172
-
173
- if stationary:
174
- raise exceptions.MapillaryStationaryVideoError("Stationary video")
175
-
176
- return points