mapillary-tools 0.13.3__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.
Files changed (87) hide show
  1. mapillary_tools/__init__.py +1 -1
  2. mapillary_tools/api_v4.py +198 -55
  3. mapillary_tools/authenticate.py +326 -64
  4. mapillary_tools/blackvue_parser.py +195 -0
  5. mapillary_tools/camm/camm_builder.py +55 -97
  6. mapillary_tools/camm/camm_parser.py +429 -181
  7. mapillary_tools/commands/__main__.py +10 -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 +18 -5
  11. mapillary_tools/commands/sample_video.py +2 -3
  12. mapillary_tools/commands/upload.py +44 -13
  13. mapillary_tools/commands/video_process_and_upload.py +19 -5
  14. mapillary_tools/config.py +65 -26
  15. mapillary_tools/constants.py +141 -18
  16. mapillary_tools/exceptions.py +37 -34
  17. mapillary_tools/exif_read.py +221 -116
  18. mapillary_tools/exif_write.py +10 -8
  19. mapillary_tools/exiftool_read.py +33 -42
  20. mapillary_tools/exiftool_read_video.py +97 -47
  21. mapillary_tools/exiftool_runner.py +57 -0
  22. mapillary_tools/ffmpeg.py +417 -242
  23. mapillary_tools/geo.py +158 -118
  24. mapillary_tools/geotag/__init__.py +0 -1
  25. mapillary_tools/geotag/base.py +147 -0
  26. mapillary_tools/geotag/factory.py +307 -0
  27. mapillary_tools/geotag/geotag_images_from_exif.py +14 -131
  28. mapillary_tools/geotag/geotag_images_from_exiftool.py +136 -85
  29. mapillary_tools/geotag/geotag_images_from_gpx.py +60 -124
  30. mapillary_tools/geotag/geotag_images_from_gpx_file.py +13 -126
  31. mapillary_tools/geotag/geotag_images_from_nmea_file.py +4 -5
  32. mapillary_tools/geotag/geotag_images_from_video.py +88 -51
  33. mapillary_tools/geotag/geotag_videos_from_exiftool.py +123 -0
  34. mapillary_tools/geotag/geotag_videos_from_gpx.py +52 -0
  35. mapillary_tools/geotag/geotag_videos_from_video.py +20 -185
  36. mapillary_tools/geotag/image_extractors/base.py +18 -0
  37. mapillary_tools/geotag/image_extractors/exif.py +60 -0
  38. mapillary_tools/geotag/image_extractors/exiftool.py +18 -0
  39. mapillary_tools/geotag/options.py +182 -0
  40. mapillary_tools/geotag/utils.py +52 -16
  41. mapillary_tools/geotag/video_extractors/base.py +18 -0
  42. mapillary_tools/geotag/video_extractors/exiftool.py +70 -0
  43. mapillary_tools/geotag/video_extractors/gpx.py +116 -0
  44. mapillary_tools/geotag/video_extractors/native.py +160 -0
  45. mapillary_tools/{geotag → gpmf}/gpmf_parser.py +205 -182
  46. mapillary_tools/{geotag → gpmf}/gps_filter.py +5 -3
  47. mapillary_tools/history.py +134 -20
  48. mapillary_tools/mp4/construct_mp4_parser.py +17 -10
  49. mapillary_tools/mp4/io_utils.py +0 -1
  50. mapillary_tools/mp4/mp4_sample_parser.py +36 -28
  51. mapillary_tools/mp4/simple_mp4_builder.py +10 -9
  52. mapillary_tools/mp4/simple_mp4_parser.py +13 -22
  53. mapillary_tools/process_geotag_properties.py +184 -414
  54. mapillary_tools/process_sequence_properties.py +594 -225
  55. mapillary_tools/sample_video.py +20 -26
  56. mapillary_tools/serializer/description.py +587 -0
  57. mapillary_tools/serializer/gpx.py +132 -0
  58. mapillary_tools/telemetry.py +26 -13
  59. mapillary_tools/types.py +98 -611
  60. mapillary_tools/upload.py +411 -387
  61. mapillary_tools/upload_api_v4.py +167 -142
  62. mapillary_tools/uploader.py +804 -284
  63. mapillary_tools/utils.py +49 -18
  64. {mapillary_tools-0.13.3.dist-info → mapillary_tools-0.14.0.dist-info}/METADATA +93 -35
  65. mapillary_tools-0.14.0.dist-info/RECORD +75 -0
  66. {mapillary_tools-0.13.3.dist-info → mapillary_tools-0.14.0.dist-info}/WHEEL +1 -1
  67. mapillary_tools/geotag/blackvue_parser.py +0 -118
  68. mapillary_tools/geotag/geotag_from_generic.py +0 -22
  69. mapillary_tools/geotag/geotag_images_from_exiftool_both_image_and_video.py +0 -93
  70. mapillary_tools/geotag/geotag_videos_from_exiftool_video.py +0 -145
  71. mapillary_tools/video_data_extraction/cli_options.py +0 -22
  72. mapillary_tools/video_data_extraction/extract_video_data.py +0 -176
  73. mapillary_tools/video_data_extraction/extractors/base_parser.py +0 -75
  74. mapillary_tools/video_data_extraction/extractors/blackvue_parser.py +0 -34
  75. mapillary_tools/video_data_extraction/extractors/camm_parser.py +0 -38
  76. mapillary_tools/video_data_extraction/extractors/exiftool_runtime_parser.py +0 -71
  77. mapillary_tools/video_data_extraction/extractors/exiftool_xml_parser.py +0 -53
  78. mapillary_tools/video_data_extraction/extractors/generic_video_parser.py +0 -52
  79. mapillary_tools/video_data_extraction/extractors/gopro_parser.py +0 -43
  80. mapillary_tools/video_data_extraction/extractors/gpx_parser.py +0 -108
  81. mapillary_tools/video_data_extraction/extractors/nmea_parser.py +0 -24
  82. mapillary_tools/video_data_extraction/video_data_parser_factory.py +0 -39
  83. mapillary_tools-0.13.3.dist-info/RECORD +0 -75
  84. /mapillary_tools/{geotag → gpmf}/gpmf_gps_filter.py +0 -0
  85. {mapillary_tools-0.13.3.dist-info → mapillary_tools-0.14.0.dist-info}/entry_points.txt +0 -0
  86. {mapillary_tools-0.13.3.dist-info → mapillary_tools-0.14.0.dist-info/licenses}/LICENSE +0 -0
  87. {mapillary_tools-0.13.3.dist-info → mapillary_tools-0.14.0.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,33 @@ 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
+ def get_file_size_quietly(path: Path) -> int:
201
+ try:
202
+ return get_file_size(path)
203
+ except Exception:
204
+ return 0
205
+
206
+
207
+ TMapIn = T.TypeVar("TMapIn")
208
+ TMapOut = T.TypeVar("TMapOut")
209
+
210
+
211
+ def mp_map_maybe(
212
+ func: T.Callable[[TMapIn], TMapOut],
213
+ iterable: T.Iterable[TMapIn],
214
+ num_processes: int | None = None,
215
+ ) -> T.Generator[TMapOut, None, None]:
216
+ if num_processes is None:
217
+ pool_num_processes = None
218
+ disable_multiprocessing = False
219
+ else:
220
+ pool_num_processes = max(num_processes, 1)
221
+ disable_multiprocessing = num_processes <= 0
222
+
223
+ if disable_multiprocessing:
224
+ yield from map(func, iterable)
225
+ else:
226
+ with Pool(processes=pool_num_processes) as pool:
227
+ yield from pool.imap(func, iterable)
@@ -1,31 +1,38 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: mapillary_tools
3
- Version: 0.13.3
3
+ Version: 0.14.0
4
4
  Summary: Mapillary Image/Video Import Pipeline
5
- Home-page: https://github.com/mapillary/mapillary_tools
6
- Author: Mapillary
5
+ Author-email: Mapillary <support@mapillary.com>
7
6
  License: BSD
8
- Requires-Python: >=3.8
7
+ Project-URL: Homepage, https://github.com/mapillary/mapillary_tools
8
+ Project-URL: Repository, https://github.com/mapillary/mapillary_tools
9
+ Project-URL: Issues, https://github.com/mapillary/mapillary_tools/issues
10
+ Keywords: mapillary,gis,computer vision,street view
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: End Users/Desktop
14
+ Classifier: License :: OSI Approved :: BSD License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Requires-Python: >=3.9
9
22
  Description-Content-Type: text/markdown
10
23
  License-File: LICENSE
11
24
  Requires-Dist: appdirs<2.0.0,>=1.4.4
12
- Requires-Dist: construct<3.0.0,>=2.10.0
13
- Requires-Dist: exifread==2.3.2
14
- Requires-Dist: piexif==1.1.3
15
- Requires-Dist: gpxpy<1.6.0,>=1.5.0
25
+ Requires-Dist: construct~=2.10.0
26
+ Requires-Dist: exifread~=3.0
27
+ Requires-Dist: gpxpy~=1.6.0
28
+ Requires-Dist: humanize>=4.12.3
29
+ Requires-Dist: jsonschema~=4.17.0
30
+ Requires-Dist: piexif~=1.1
16
31
  Requires-Dist: pynmea2<2.0.0,>=1.12.0
17
32
  Requires-Dist: requests[socks]<3.0.0,>=2.20.0
18
33
  Requires-Dist: tqdm<5.0,>=4.0
19
- Requires-Dist: typing_extensions
20
- Requires-Dist: jsonschema~=4.17.3
21
- Dynamic: author
22
- Dynamic: description
23
- Dynamic: description-content-type
24
- Dynamic: home-page
25
- Dynamic: license
26
- Dynamic: requires-dist
27
- Dynamic: requires-python
28
- Dynamic: summary
34
+ Requires-Dist: typing-extensions>=4.12.2
35
+ Dynamic: license-file
29
36
 
30
37
  <p align="center">
31
38
  <a href="https://github.com/mapillary/mapillary_tools/">
@@ -57,18 +64,40 @@ mapillary_tools --help
57
64
  <!--ts-->
58
65
 
59
66
  - [Supported File Formats](#supported-file-formats)
67
+ - [Image Formats](#image-formats)
68
+ - [Video Formats](#video-formats)
60
69
  - [Installation](#installation)
70
+ - [Standalone Executable](#standalone-executable)
71
+ - [Installing via pip](#installing-via-pip)
72
+ - [Installing on Android Devices](#installing-on-android-devices)
61
73
  - [Usage](#usage)
62
74
  - [Process and Upload](#process-and-upload)
63
75
  - [Process](#process)
64
76
  - [Upload](#upload)
65
77
  - [Advanced Usage](#advanced-usage)
66
78
  - [Local Video Processing](#local-video-processing)
79
+ - [Install FFmpeg](#install-ffmpeg)
80
+ - [Video Processing](#video-processing)
67
81
  - [Geotagging with GPX](#geotagging-with-gpx)
82
+ - [New video geotagging features (experimental)](#new-video-geotagging-features-experimental)
83
+ - [Usage](#usage-1)
84
+ - [Examples](#examples)
85
+ - [Generic supported videos](#generic-supported-videos)
86
+ - [External GPX](#external-gpx)
87
+ - [Insta360 stitched videos](#insta360-stitched-videos)
88
+ - [Limitations of `--video_geotag_source`](#limitations-of---video_geotag_source)
68
89
  - [Authenticate](#authenticate)
90
+ - [Examples](#examples-1)
69
91
  - [Image Description](#image-description)
70
92
  - [Zip Images](#zip-images)
71
93
  - [Development](#development)
94
+ - [Setup](#setup)
95
+ - [Option 1: Using uv (Recommended)](#option-1-using-uv-recommended)
96
+ - [Option 2: Using pip with virtual environment](#option-2-using-pip-with-virtual-environment)
97
+ - [Running the code](#running-the-code)
98
+ - [Tests](#tests)
99
+ - [Code Quality](#code-quality)
100
+ - [Release and Build](#release-and-build)
72
101
 
73
102
  <!--te-->
74
103
 
@@ -494,29 +523,49 @@ git clone git@github.com:mapillary/mapillary_tools.git
494
523
  cd mapillary_tools
495
524
  ```
496
525
 
497
- Set up the virtual environment. It is optional but recommended:
526
+ ### Option 1: Using uv (Recommended)
527
+
528
+ Use [uv](https://docs.astral.sh/uv/) - a fast Python package manager.
529
+
530
+ Install the project in development mode with all dependencies:
498
531
 
499
532
  ```sh
500
- pip install pipenv
533
+ # Install the project and development dependencies
534
+ uv sync --group dev
535
+
536
+ # Activate the virtual environment
537
+ source .venv/bin/activate # On Windows: .venv\Scripts\activate
501
538
  ```
502
539
 
503
- Install dependencies:
540
+ ### Option 2: Using pip with virtual environment
541
+
542
+ Set up a virtual environment (recommended):
504
543
 
505
544
  ```sh
506
- pipenv install -r requirements.txt
507
- pipenv install -r requirements-dev.txt
545
+ python -m venv .venv
546
+ source .venv/bin/activate # On Windows: .venv\Scripts\activate
508
547
  ```
509
548
 
510
- Enter the virtualenv shell:
549
+ Install the project in development mode:
511
550
 
512
551
  ```sh
513
- pipenv shell
552
+ # Install the project and all dependencies in editable mode
553
+ pip install -e .
554
+
555
+ # Install development dependencies
556
+ pip install --group dev
514
557
  ```
515
558
 
559
+ ## Running the code
560
+
516
561
  Run the code from the repository:
517
562
 
518
563
  ```sh
519
- python3 -m mapillary_tools.commands --version
564
+ # If you have mapillary_tools installed in editable mode
565
+ mapillary_tools --version
566
+
567
+ # Alternatively
568
+ python -m mapillary_tools.commands --version
520
569
  ```
521
570
 
522
571
  ## Tests
@@ -524,19 +573,28 @@ python3 -m mapillary_tools.commands --version
524
573
  Run tests:
525
574
 
526
575
  ```sh
527
- # test all cases
528
- python3 -m pytest -s -vv tests
529
- # or test a single case specifically
530
- python3 -m pytest -s -vv tests/unit/test_camm_parser.py::test_build_and_parse
576
+ # Test all cases
577
+ pytest -s -vv tests
578
+ # Or test a single case specifically
579
+ pytest -s -vv tests/unit/test_camm_parser.py::test_build_and_parse
531
580
  ```
532
581
 
533
- Run linting:
582
+ ## Code Quality
583
+
584
+ Run code formatting and linting:
534
585
 
535
586
  ```sh
536
- # format code
537
- black mapillary_tools tests
538
- # sort imports
587
+ # Format code with ruff
588
+ ruff format mapillary_tools tests
589
+
590
+ # Lint code with ruff
591
+ ruff check mapillary_tools tests
592
+
593
+ # Sort imports with usort
539
594
  usort format mapillary_tools tests
595
+
596
+ # Type checking with mypy
597
+ mypy mapillary_tools
540
598
  ```
541
599
 
542
600
  ## Release and Build
@@ -0,0 +1,75 @@
1
+ mapillary_tools/__init__.py,sha256=_3t9ZCm4vuKINk3jDtMZj_Wcw_4NcgFIpwhwW-DVWec,19
2
+ mapillary_tools/api_v4.py,sha256=gDhFa2Jz6fGbPrtSrBjtPIXRXOxqWaXvcVCJT8KyRww,11788
3
+ mapillary_tools/authenticate.py,sha256=OKS4jDFMo7XCzk9jEeHyZZUy-q56GoUZytJ5ta4QI8E,11219
4
+ mapillary_tools/blackvue_parser.py,sha256=ea2JtU9MWU6yB0bQlF970_Of0bJVofSTRq1P30WKW-0,5623
5
+ mapillary_tools/config.py,sha256=97zsyPnZXGincEkM__c6UNWH25EMTtRKZGsp6vBpHy8,3326
6
+ mapillary_tools/constants.py,sha256=UuEuSajD3GvV73D5QRnjFlC6eDzQZbtQShaE2l98x18,6080
7
+ mapillary_tools/exceptions.py,sha256=uxTgBEfXgGxT0XNGRIAZ5mjtdqsCYfP6gnaXAK_ewBM,2483
8
+ mapillary_tools/exif_read.py,sha256=gagyRv06RkZPRHQhyV-5-uQzxHgxOsVqUX2aC8QmCyM,32145
9
+ mapillary_tools/exif_write.py,sha256=I9GSs8KRd28QyKPVNHuxesSVbBvahHqKBS3nw9HoLsg,8830
10
+ mapillary_tools/exiftool_read.py,sha256=5uatYE9mgbg2d9NAnPfX22nSRBjXhJ9ayMqNMd4QwGM,15779
11
+ mapillary_tools/exiftool_read_video.py,sha256=23O_bjUOVq6j7i3xMz6fY-XIEsjinsCejK_0nrbFyOM,16084
12
+ mapillary_tools/exiftool_runner.py,sha256=g4gSyqeh3D6EnMJ-c3s-RnO2EP_jD354Qkaz0Y-4D04,1658
13
+ mapillary_tools/ffmpeg.py,sha256=akpvvsjAR-Iiv-hOrUoJvPM9vUU3JqMQ5HJL1_NgwB8,22908
14
+ mapillary_tools/geo.py,sha256=mWaESfDf_zHmyvnt5aVFro4FGrjiULNsuZ6HfGUWvSA,11009
15
+ mapillary_tools/history.py,sha256=LP6e0zEYVBwRGUbFaGoE_AaBIEdpB4XrZsg9qwJVvRI,5344
16
+ mapillary_tools/ipc.py,sha256=DwWQb9hNshx0bg0Fo5NjY0mXjs-FkbR6tIQmjMgMtmg,1089
17
+ mapillary_tools/process_geotag_properties.py,sha256=uT7dMpCujpBIysKd26sQRsIh8sVAkNv2DnzZQmEOmt0,14263
18
+ mapillary_tools/process_sequence_properties.py,sha256=n4VjQHrgVjksIr3WoBviRhrQIBBDHGXMClolfyz6tu4,24057
19
+ mapillary_tools/sample_video.py,sha256=gViQQ4CSHihzk42kGq_Rx4sr9sBPsGvPXXx2Bgj5grU,13980
20
+ mapillary_tools/telemetry.py,sha256=lL6qQbtOZft4DZZrCNK3njlwHT_30zLyYS_YRN5pgHY,1568
21
+ mapillary_tools/types.py,sha256=pIU2wcxiOUWT5Pd05pgNzY9EVEDlwoldtlF2IIYYvE0,5909
22
+ mapillary_tools/upload.py,sha256=fOJ_cdPspVeGbqiATP4cLTcvR6tx3eIUlQxzADh863Q,23815
23
+ mapillary_tools/upload_api_v4.py,sha256=ZpqWZ0WFiNCx3JUinLW7xKYkf4Tc-6oe3nE1qkXm898,7539
24
+ mapillary_tools/uploader.py,sha256=lVauLejQd8cL7BdvR8z_i28t5-yxDVVEt_xlM43KUgs,32064
25
+ mapillary_tools/utils.py,sha256=eqHVmaLDjEDYeLR-4b3gmg4_afoVi1RynVn8zQMFlt4,6773
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=-tm3p90JMAIQ9o86_MRCA40_WffOYGacTGWxBNzzEVA,5026
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=p2c_QGPJWrSaXyqjwC1Qh3yzV0Zy2V3wVIfTJx3cwYE,2819
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/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
+ mapillary_tools/geotag/base.py,sha256=19-3P5nbFehOxacTT042nQUITzGnXndgnW_mXLO5q9s,4948
40
+ mapillary_tools/geotag/factory.py,sha256=MuuLb9gcGWs7LHywyn8cGsml4eupdVQjji9jj_QCujk,10218
41
+ mapillary_tools/geotag/geotag_images_from_exif.py,sha256=a5fDOp4tlEoVNwrOCJBrvSPzu1LbyxL4RNrNDD0tPP0,608
42
+ mapillary_tools/geotag/geotag_images_from_exiftool.py,sha256=TTJb9tq1kYSPn_gwDDAcviueYwoUIbIEoLl-abAB8T8,5716
43
+ mapillary_tools/geotag/geotag_images_from_gpx.py,sha256=EyhJjpP1L_DPRCX926aKeTvuxFxD-eWVCWgELp0-pzY,6313
44
+ mapillary_tools/geotag/geotag_images_from_gpx_file.py,sha256=HYQkwak32YBDuRrTNiIZOmE3iImCRc22HWWb485WRS4,1121
45
+ mapillary_tools/geotag/geotag_images_from_nmea_file.py,sha256=J8xj6ch9bMPRubJtsRGeb3sb9LyB0ZYy65NPEOVkUe8,1597
46
+ mapillary_tools/geotag/geotag_images_from_video.py,sha256=3NV3-NfSkxT0n_n8Ajqjab24x29H2vL98DpwJqnIvT8,4411
47
+ mapillary_tools/geotag/geotag_videos_from_exiftool.py,sha256=ge29zKj9jLQFLmmzb0IyE69wenuwJ3XGmMyANT0UIAI,4288
48
+ mapillary_tools/geotag/geotag_videos_from_gpx.py,sha256=IoV7asxl7dojF1lftvohm1jK_LboFg_mBz25GiV_CsY,1653
49
+ mapillary_tools/geotag/geotag_videos_from_video.py,sha256=T8XS4lVF2Wz4eDXNi5Vt076M5dxjxJXibVrWhqVvErs,863
50
+ mapillary_tools/geotag/options.py,sha256=0CSQUuhVE-WdAVQXWXjwMtxujW1yB6yxRkFmVKH8LkQ,5092
51
+ mapillary_tools/geotag/utils.py,sha256=0DenLVCLP-GZdk0rCFP0V1tjMGK4XAvYro3I_1RKgCA,1733
52
+ mapillary_tools/geotag/image_extractors/base.py,sha256=XoNrLCbJtd-MN-snbhv6zyr6zBfJRoJkntV0ptrh6qg,358
53
+ mapillary_tools/geotag/image_extractors/exif.py,sha256=cCUegbFqWxjAP4oOP1nZmwoJISWeKgjGO8h_t7nucHs,2079
54
+ mapillary_tools/geotag/image_extractors/exiftool.py,sha256=zokJmf-D2rPvASRJs3dZzEu7j82igpMOr4SE6Z1nsVg,497
55
+ mapillary_tools/geotag/video_extractors/base.py,sha256=KxyKxoh_mV-XBnJq3dSzNEt2rQqZAKjmS3GazsdYbnc,350
56
+ mapillary_tools/geotag/video_extractors/exiftool.py,sha256=mH9arsWBaArYhNMxnrRpoEa3YrMjSpCiZaz-baJRMCo,2305
57
+ mapillary_tools/geotag/video_extractors/gpx.py,sha256=jE3UQs6rTzIElPBvS7eKgR83JAZnbteXM1V5TbNCdyY,3753
58
+ mapillary_tools/geotag/video_extractors/native.py,sha256=_xmTQ6nrvdwkadMmIxrhw6rvGTGwQVmrdSkYLhhV1SM,5786
59
+ mapillary_tools/gpmf/gpmf_gps_filter.py,sha256=7cg8wEjC1DrujKY76FZguXsaPqTRkG9-t32OeuOJQIc,2755
60
+ mapillary_tools/gpmf/gpmf_parser.py,sha256=uOVXkwJxC3Y2YfTdzUDpt7Bh0pdVqa5u0WUuv7pEJEs,24670
61
+ mapillary_tools/gpmf/gps_filter.py,sha256=zhXkuvr0Dd1bxGTYBwvk6P7xasY_RLuWjHaX7CdBayc,3796
62
+ mapillary_tools/mp4/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
63
+ mapillary_tools/mp4/construct_mp4_parser.py,sha256=jS8fOgSByqieXA6C5ZqobaI0bK17U_YAkiWEUK_8j4E,17286
64
+ mapillary_tools/mp4/io_utils.py,sha256=KZaJTlgFS27Oh3pcA5MKXYFoCifqgFaEZJyU6lb1jc4,5416
65
+ mapillary_tools/mp4/mp4_sample_parser.py,sha256=0ILTq8M6mXFTI3agKgljpvO9uYa7HXGUGZpdHT8a6ac,11547
66
+ mapillary_tools/mp4/simple_mp4_builder.py,sha256=9TUGk1hzI6mQFN1P30jwHL3dCYz3Zz7rsm8UBvMAqMc,12734
67
+ mapillary_tools/mp4/simple_mp4_parser.py,sha256=g3vvPhBoNu7anhVzC5_XQCV7IwfRWro1vJ6d6GyDkHE,6315
68
+ mapillary_tools/serializer/description.py,sha256=FB-DawgHVW5M2e81Uo921bY7DQv9fogXF3QMilpmEhQ,18473
69
+ mapillary_tools/serializer/gpx.py,sha256=_xx6gHjaWHrlXaUpB5GGBrbRKzbExFyIzWWAH-CvksI,4383
70
+ mapillary_tools-0.14.0.dist-info/licenses/LICENSE,sha256=l2D8cKfFmmJq_wcVq_JElPJrlvWQOzNWx7gMLINucxc,1292
71
+ mapillary_tools-0.14.0.dist-info/METADATA,sha256=QfSr8vy3dnl6HBeZjtYWgYoYpbJohbipNXmIio8lnO8,22200
72
+ mapillary_tools-0.14.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
73
+ mapillary_tools-0.14.0.dist-info/entry_points.txt,sha256=A3f3LP-BO_P-U8Y29QfpT4jx6Mjk3sXjTi2Yew4bvj8,75
74
+ mapillary_tools-0.14.0.dist-info/top_level.txt,sha256=FbDkMgOrt1S70ho1WSBrOwzKOSkJFDwwqFOoY5-527s,16
75
+ mapillary_tools-0.14.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (76.0.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,118 +0,0 @@
1
- import json
2
- import logging
3
- import pathlib
4
- import re
5
- import typing as T
6
-
7
- import pynmea2
8
-
9
- from .. import geo
10
- from ..mp4 import simple_mp4_parser as sparser
11
-
12
-
13
- LOG = logging.getLogger(__name__)
14
- # An example: [1623057074211]$GPVTG,,T,,M,0.078,N,0.144,K,D*28[1623057075215]
15
- NMEA_LINE_REGEX = re.compile(
16
- rb"""
17
- ^\s*
18
- \[(\d+)\] # timestamp
19
- \s*
20
- (\$\w{5}.*) # nmea line
21
- \s*
22
- (\[\d+\])? # strange timestamp
23
- \s*$
24
- """,
25
- re.X,
26
- )
27
-
28
-
29
- def _parse_gps_box(gps_data: bytes) -> T.Generator[geo.Point, None, None]:
30
- for line_bytes in gps_data.splitlines():
31
- match = NMEA_LINE_REGEX.match(line_bytes)
32
- if match is None:
33
- continue
34
- nmea_line_bytes = match.group(2)
35
- if nmea_line_bytes.startswith(b"$GPGGA"):
36
- try:
37
- nmea_line = nmea_line_bytes.decode("utf8")
38
- except UnicodeDecodeError:
39
- continue
40
- try:
41
- nmea = pynmea2.parse(nmea_line)
42
- except pynmea2.nmea.ParseError:
43
- continue
44
- if not nmea.is_valid:
45
- continue
46
- epoch_ms = int(match.group(1))
47
- yield geo.Point(
48
- time=epoch_ms,
49
- lat=nmea.latitude,
50
- lon=nmea.longitude,
51
- alt=nmea.altitude,
52
- angle=None,
53
- )
54
-
55
-
56
- def extract_camera_model(fp: T.BinaryIO) -> str:
57
- try:
58
- cprt_bytes = sparser.parse_mp4_data_first(fp, [b"free", b"cprt"])
59
- except sparser.ParsingError:
60
- return ""
61
-
62
- if cprt_bytes is None:
63
- return ""
64
-
65
- # examples: b' {"model":"DR900X Plus","ver":0.918,"lang":"English","direct":1,"psn":"","temp":34,"GPS":1}\x00'
66
- # b' Pittasoft Co., Ltd.;DR900S-1CH;1.008;English;1;D90SS1HAE00661;T69;\x00'
67
- cprt_bytes = cprt_bytes.strip().strip(b"\x00")
68
-
69
- try:
70
- cprt_str = cprt_bytes.decode("utf8")
71
- except UnicodeDecodeError:
72
- return ""
73
-
74
- try:
75
- cprt_json = json.loads(cprt_str)
76
- except json.JSONDecodeError:
77
- cprt_json = None
78
-
79
- if cprt_json is not None:
80
- return str(cprt_json.get("model", "")).strip()
81
-
82
- fields = cprt_str.split(";")
83
- if 2 <= len(fields):
84
- model = fields[1]
85
- if model:
86
- return model.strip()
87
- else:
88
- return ""
89
- else:
90
- return ""
91
-
92
-
93
- def extract_points(fp: T.BinaryIO) -> T.Optional[T.List[geo.Point]]:
94
- gps_data = sparser.parse_mp4_data_first(fp, [b"free", b"gps "])
95
- if gps_data is None:
96
- return None
97
-
98
- points = list(_parse_gps_box(gps_data))
99
- if not points:
100
- return points
101
-
102
- points.sort(key=lambda p: p.time)
103
-
104
- first_point_time = points[0].time
105
- for p in points:
106
- p.time = (p.time - first_point_time) / 1000
107
-
108
- return points
109
-
110
-
111
- def parse_gps_points(path: pathlib.Path) -> T.List[geo.Point]:
112
- with path.open("rb") as fp:
113
- points = extract_points(fp)
114
-
115
- if points is None:
116
- return []
117
-
118
- return points
@@ -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