mapillary-tools 0.14.0a1__py3-none-any.whl → 0.14.0b1__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 (76) hide show
  1. mapillary_tools/__init__.py +1 -1
  2. mapillary_tools/api_v4.py +5 -4
  3. mapillary_tools/authenticate.py +9 -9
  4. mapillary_tools/blackvue_parser.py +79 -22
  5. mapillary_tools/camm/camm_parser.py +5 -5
  6. mapillary_tools/commands/__main__.py +1 -2
  7. mapillary_tools/config.py +41 -18
  8. mapillary_tools/constants.py +3 -2
  9. mapillary_tools/exceptions.py +1 -1
  10. mapillary_tools/exif_read.py +65 -65
  11. mapillary_tools/exif_write.py +7 -7
  12. mapillary_tools/exiftool_read.py +23 -46
  13. mapillary_tools/exiftool_read_video.py +88 -49
  14. mapillary_tools/exiftool_runner.py +4 -24
  15. mapillary_tools/ffmpeg.py +417 -242
  16. mapillary_tools/geo.py +4 -21
  17. mapillary_tools/geotag/__init__.py +0 -1
  18. mapillary_tools/geotag/{geotag_from_generic.py → base.py} +34 -50
  19. mapillary_tools/geotag/factory.py +105 -103
  20. mapillary_tools/geotag/geotag_images_from_exif.py +15 -51
  21. mapillary_tools/geotag/geotag_images_from_exiftool.py +118 -63
  22. mapillary_tools/geotag/geotag_images_from_gpx.py +33 -16
  23. mapillary_tools/geotag/geotag_images_from_gpx_file.py +2 -34
  24. mapillary_tools/geotag/geotag_images_from_nmea_file.py +0 -3
  25. mapillary_tools/geotag/geotag_images_from_video.py +51 -14
  26. mapillary_tools/geotag/geotag_videos_from_exiftool.py +123 -0
  27. mapillary_tools/geotag/geotag_videos_from_gpx.py +35 -123
  28. mapillary_tools/geotag/geotag_videos_from_video.py +14 -147
  29. mapillary_tools/geotag/image_extractors/base.py +18 -0
  30. mapillary_tools/geotag/image_extractors/exif.py +60 -0
  31. mapillary_tools/geotag/image_extractors/exiftool.py +18 -0
  32. mapillary_tools/geotag/options.py +26 -3
  33. mapillary_tools/geotag/utils.py +62 -0
  34. mapillary_tools/geotag/video_extractors/base.py +18 -0
  35. mapillary_tools/geotag/video_extractors/exiftool.py +70 -0
  36. mapillary_tools/geotag/video_extractors/gpx.py +116 -0
  37. mapillary_tools/geotag/video_extractors/native.py +135 -0
  38. mapillary_tools/gpmf/gpmf_parser.py +16 -16
  39. mapillary_tools/gpmf/gps_filter.py +5 -3
  40. mapillary_tools/history.py +8 -3
  41. mapillary_tools/mp4/construct_mp4_parser.py +9 -8
  42. mapillary_tools/mp4/mp4_sample_parser.py +27 -27
  43. mapillary_tools/mp4/simple_mp4_builder.py +10 -9
  44. mapillary_tools/mp4/simple_mp4_parser.py +13 -12
  45. mapillary_tools/process_geotag_properties.py +21 -15
  46. mapillary_tools/process_sequence_properties.py +49 -49
  47. mapillary_tools/sample_video.py +15 -14
  48. mapillary_tools/serializer/description.py +587 -0
  49. mapillary_tools/serializer/gpx.py +132 -0
  50. mapillary_tools/telemetry.py +6 -5
  51. mapillary_tools/types.py +64 -635
  52. mapillary_tools/upload.py +176 -197
  53. mapillary_tools/upload_api_v4.py +94 -51
  54. mapillary_tools/uploader.py +284 -138
  55. mapillary_tools/utils.py +16 -18
  56. {mapillary_tools-0.14.0a1.dist-info → mapillary_tools-0.14.0b1.dist-info}/METADATA +87 -31
  57. mapillary_tools-0.14.0b1.dist-info/RECORD +75 -0
  58. {mapillary_tools-0.14.0a1.dist-info → mapillary_tools-0.14.0b1.dist-info}/WHEEL +1 -1
  59. mapillary_tools/geotag/geotag_images_from_exiftool_both_image_and_video.py +0 -77
  60. mapillary_tools/geotag/geotag_videos_from_exiftool_video.py +0 -151
  61. mapillary_tools/video_data_extraction/cli_options.py +0 -22
  62. mapillary_tools/video_data_extraction/extract_video_data.py +0 -157
  63. mapillary_tools/video_data_extraction/extractors/base_parser.py +0 -75
  64. mapillary_tools/video_data_extraction/extractors/blackvue_parser.py +0 -49
  65. mapillary_tools/video_data_extraction/extractors/camm_parser.py +0 -62
  66. mapillary_tools/video_data_extraction/extractors/exiftool_runtime_parser.py +0 -74
  67. mapillary_tools/video_data_extraction/extractors/exiftool_xml_parser.py +0 -52
  68. mapillary_tools/video_data_extraction/extractors/generic_video_parser.py +0 -52
  69. mapillary_tools/video_data_extraction/extractors/gopro_parser.py +0 -58
  70. mapillary_tools/video_data_extraction/extractors/gpx_parser.py +0 -108
  71. mapillary_tools/video_data_extraction/extractors/nmea_parser.py +0 -24
  72. mapillary_tools/video_data_extraction/video_data_parser_factory.py +0 -39
  73. mapillary_tools-0.14.0a1.dist-info/RECORD +0 -78
  74. {mapillary_tools-0.14.0a1.dist-info → mapillary_tools-0.14.0b1.dist-info}/entry_points.txt +0 -0
  75. {mapillary_tools-0.14.0a1.dist-info → mapillary_tools-0.14.0b1.dist-info}/licenses/LICENSE +0 -0
  76. {mapillary_tools-0.14.0a1.dist-info → mapillary_tools-0.14.0b1.dist-info}/top_level.txt +0 -0
mapillary_tools/utils.py CHANGED
@@ -9,9 +9,7 @@ from pathlib import Path
9
9
 
10
10
  # Use "hashlib._Hash" instead of hashlib._Hash because:
11
11
  # AttributeError: module 'hashlib' has no attribute '_Hash'
12
- def md5sum_fp(
13
- fp: T.IO[bytes], md5: T.Optional["hashlib._Hash"] = None
14
- ) -> "hashlib._Hash":
12
+ def md5sum_fp(fp: T.IO[bytes], md5: "hashlib._Hash | None" = None) -> "hashlib._Hash":
15
13
  if md5 is None:
16
14
  md5 = hashlib.md5()
17
15
  while True:
@@ -93,12 +91,12 @@ def filter_video_samples(
93
91
 
94
92
 
95
93
  def find_all_image_samples(
96
- image_paths: T.Sequence[Path], video_paths: T.Sequence[Path]
97
- ) -> T.Dict[Path, T.List[Path]]:
94
+ image_paths: T.Iterable[Path], video_paths: T.Iterable[Path]
95
+ ) -> dict[Path, list[Path]]:
98
96
  # TODO: not work with the same filenames, e.g. foo/hello.mp4 and bar/hello.mp4
99
97
  video_basenames = {path.name: path for path in video_paths}
100
98
 
101
- image_samples_by_video_path: T.Dict[Path, T.List[Path]] = {}
99
+ image_samples_by_video_path: dict[Path, list[Path]] = {}
102
100
  for image_path in image_paths:
103
101
  # If you want to walk an arbitrary filesystem path upwards,
104
102
  # it is recommended to first call Path.resolve() so as to resolve symlinks and eliminate “..” components.
@@ -115,7 +113,7 @@ def find_all_image_samples(
115
113
 
116
114
 
117
115
  def deduplicate_paths(paths: T.Iterable[Path]) -> T.Generator[Path, None, None]:
118
- resolved_paths: T.Set[Path] = set()
116
+ resolved_paths: set[Path] = set()
119
117
  for p in paths:
120
118
  resolved = p.resolve()
121
119
  if resolved not in resolved_paths:
@@ -124,10 +122,10 @@ def deduplicate_paths(paths: T.Iterable[Path]) -> T.Generator[Path, None, None]:
124
122
 
125
123
 
126
124
  def find_images(
127
- import_paths: T.Sequence[Path],
125
+ import_paths: T.Iterable[Path],
128
126
  skip_subfolders: bool = False,
129
- ) -> T.List[Path]:
130
- image_paths: T.List[Path] = []
127
+ ) -> list[Path]:
128
+ image_paths: list[Path] = []
131
129
  for path in import_paths:
132
130
  if path.is_dir():
133
131
  image_paths.extend(
@@ -142,10 +140,10 @@ def find_images(
142
140
 
143
141
 
144
142
  def find_videos(
145
- import_paths: T.Sequence[Path],
143
+ import_paths: T.Iterable[Path],
146
144
  skip_subfolders: bool = False,
147
- ) -> T.List[Path]:
148
- video_paths: T.List[Path] = []
145
+ ) -> list[Path]:
146
+ video_paths: list[Path] = []
149
147
  for path in import_paths:
150
148
  if path.is_dir():
151
149
  video_paths.extend(
@@ -160,10 +158,10 @@ def find_videos(
160
158
 
161
159
 
162
160
  def find_zipfiles(
163
- import_paths: T.Sequence[Path],
161
+ import_paths: T.Iterable[Path],
164
162
  skip_subfolders: bool = False,
165
- ) -> T.List[Path]:
166
- zip_paths: T.List[Path] = []
163
+ ) -> list[Path]:
164
+ zip_paths: list[Path] = []
167
165
  for path in import_paths:
168
166
  if path.is_dir():
169
167
  zip_paths.extend(
@@ -177,8 +175,8 @@ def find_zipfiles(
177
175
  return list(deduplicate_paths(zip_paths))
178
176
 
179
177
 
180
- def find_xml_files(import_paths: T.Sequence[Path]) -> T.List[Path]:
181
- xml_paths: T.List[Path] = []
178
+ def find_xml_files(import_paths: T.Iterable[Path]) -> list[Path]:
179
+ xml_paths: list[Path] = []
182
180
  for path in import_paths:
183
181
  if path.is_dir():
184
182
  # XML could be hidden in hidden folders
@@ -1,32 +1,37 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mapillary_tools
3
- Version: 0.14.0a1
3
+ Version: 0.14.0b1
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
25
  Requires-Dist: construct<3.0.0,>=2.10.0
13
26
  Requires-Dist: exifread==2.3.2
14
- Requires-Dist: piexif==1.1.3
15
27
  Requires-Dist: gpxpy<1.6.0,>=1.5.0
28
+ Requires-Dist: jsonschema~=4.17.3
29
+ Requires-Dist: piexif==1.1.3
16
30
  Requires-Dist: pynmea2<2.0.0,>=1.12.0
17
31
  Requires-Dist: requests[socks]<3.0.0,>=2.20.0
18
32
  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
33
+ Requires-Dist: typing-extensions>=4.12.2
26
34
  Dynamic: license-file
27
- Dynamic: requires-dist
28
- Dynamic: requires-python
29
- Dynamic: summary
30
35
 
31
36
  <p align="center">
32
37
  <a href="https://github.com/mapillary/mapillary_tools/">
@@ -58,18 +63,40 @@ mapillary_tools --help
58
63
  <!--ts-->
59
64
 
60
65
  - [Supported File Formats](#supported-file-formats)
66
+ - [Image Formats](#image-formats)
67
+ - [Video Formats](#video-formats)
61
68
  - [Installation](#installation)
69
+ - [Standalone Executable](#standalone-executable)
70
+ - [Installing via pip](#installing-via-pip)
71
+ - [Installing on Android Devices](#installing-on-android-devices)
62
72
  - [Usage](#usage)
63
73
  - [Process and Upload](#process-and-upload)
64
74
  - [Process](#process)
65
75
  - [Upload](#upload)
66
76
  - [Advanced Usage](#advanced-usage)
67
77
  - [Local Video Processing](#local-video-processing)
78
+ - [Install FFmpeg](#install-ffmpeg)
79
+ - [Video Processing](#video-processing)
68
80
  - [Geotagging with GPX](#geotagging-with-gpx)
81
+ - [New video geotagging features (experimental)](#new-video-geotagging-features-experimental)
82
+ - [Usage](#usage-1)
83
+ - [Examples](#examples)
84
+ - [Generic supported videos](#generic-supported-videos)
85
+ - [External GPX](#external-gpx)
86
+ - [Insta360 stitched videos](#insta360-stitched-videos)
87
+ - [Limitations of `--video_geotag_source`](#limitations-of---video_geotag_source)
69
88
  - [Authenticate](#authenticate)
89
+ - [Examples](#examples-1)
70
90
  - [Image Description](#image-description)
71
91
  - [Zip Images](#zip-images)
72
92
  - [Development](#development)
93
+ - [Setup](#setup)
94
+ - [Option 1: Using uv (Recommended)](#option-1-using-uv-recommended)
95
+ - [Option 2: Using pip with virtual environment](#option-2-using-pip-with-virtual-environment)
96
+ - [Running the code](#running-the-code)
97
+ - [Tests](#tests)
98
+ - [Code Quality](#code-quality)
99
+ - [Release and Build](#release-and-build)
73
100
 
74
101
  <!--te-->
75
102
 
@@ -495,29 +522,49 @@ git clone git@github.com:mapillary/mapillary_tools.git
495
522
  cd mapillary_tools
496
523
  ```
497
524
 
498
- Set up the virtual environment. It is optional but recommended:
525
+ ### Option 1: Using uv (Recommended)
526
+
527
+ Use [uv](https://docs.astral.sh/uv/) - a fast Python package manager.
528
+
529
+ Install the project in development mode with all dependencies:
499
530
 
500
531
  ```sh
501
- pip install pipenv
532
+ # Install the project and development dependencies
533
+ uv sync --group dev
534
+
535
+ # Activate the virtual environment
536
+ source .venv/bin/activate # On Windows: .venv\Scripts\activate
502
537
  ```
503
538
 
504
- Install dependencies:
539
+ ### Option 2: Using pip with virtual environment
540
+
541
+ Set up a virtual environment (recommended):
505
542
 
506
543
  ```sh
507
- pipenv install -r requirements.txt
508
- pipenv install -r requirements-dev.txt
544
+ python -m venv .venv
545
+ source .venv/bin/activate # On Windows: .venv\Scripts\activate
509
546
  ```
510
547
 
511
- Enter the virtualenv shell:
548
+ Install the project in development mode:
512
549
 
513
550
  ```sh
514
- pipenv shell
551
+ # Install the project and all dependencies in editable mode
552
+ pip install -e .
553
+
554
+ # Install development dependencies
555
+ pip install --group dev
515
556
  ```
516
557
 
558
+ ## Running the code
559
+
517
560
  Run the code from the repository:
518
561
 
519
562
  ```sh
520
- python3 -m mapillary_tools.commands --version
563
+ # If you have mapillary_tools installed in editable mode
564
+ mapillary_tools --version
565
+
566
+ # Alternatively
567
+ python -m mapillary_tools.commands --version
521
568
  ```
522
569
 
523
570
  ## Tests
@@ -525,19 +572,28 @@ python3 -m mapillary_tools.commands --version
525
572
  Run tests:
526
573
 
527
574
  ```sh
528
- # test all cases
529
- python3 -m pytest -s -vv tests
530
- # or test a single case specifically
531
- python3 -m pytest -s -vv tests/unit/test_camm_parser.py::test_build_and_parse
575
+ # Test all cases
576
+ pytest -s -vv tests
577
+ # Or test a single case specifically
578
+ pytest -s -vv tests/unit/test_camm_parser.py::test_build_and_parse
532
579
  ```
533
580
 
534
- Run linting:
581
+ ## Code Quality
582
+
583
+ Run code formatting and linting:
535
584
 
536
585
  ```sh
537
- # format code
538
- black mapillary_tools tests
539
- # sort imports
586
+ # Format code with ruff
587
+ ruff format mapillary_tools tests
588
+
589
+ # Lint code with ruff
590
+ ruff check mapillary_tools tests
591
+
592
+ # Sort imports with usort
540
593
  usort format mapillary_tools tests
594
+
595
+ # Type checking with mypy
596
+ mypy mapillary_tools
541
597
  ```
542
598
 
543
599
  ## Release and Build
@@ -0,0 +1,75 @@
1
+ mapillary_tools/__init__.py,sha256=BXYNPTo5QIOW_UpAqv3arB1SDKpH8qZZAzzNEEAG4Io,21
2
+ mapillary_tools/api_v4.py,sha256=2Xb72FcddR0KEohH_zulRlHaGzb1BgqYt84r0IiHr4c,10139
3
+ mapillary_tools/authenticate.py,sha256=rO39j--CSGTXnYWOKOgkXKvK7l7p7Nnh1bnbihDyQ_U,11094
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=KvTizvc4PLQhU7RDB3Eabgr2B4cIvRfV4mCM05YeB9Y,3859
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=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=MSfkjSAgTAeuBqncQ8-QXvDuJ7ar7bTt5P_e9HDIgVs,10604
15
+ mapillary_tools/history.py,sha256=5Zk4iXSXny9kImzOKrnZmZ3sDjXtCcnJC5pzqHcf-Vk,1753
16
+ mapillary_tools/ipc.py,sha256=DwWQb9hNshx0bg0Fo5NjY0mXjs-FkbR6tIQmjMgMtmg,1089
17
+ mapillary_tools/process_geotag_properties.py,sha256=ND4Mz8NIIhbiD0aFk4Eo33WRpt3VYwCBbOBaxmP-rbU,14246
18
+ mapillary_tools/process_sequence_properties.py,sha256=XqddHPsj5EjIKbu8omqPcufWdEbrMS_MNf8se5DXL1M,23480
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=s29bRwYuaZuTuy3TIBspgCwA8ueYN-RiDvOFkWI-kfY,24411
23
+ mapillary_tools/upload_api_v4.py,sha256=4aw0uMkKL-zhQ-jPv0GgDvnYSX1DA8zRk4TOFyU9EJA,7753
24
+ mapillary_tools/uploader.py,sha256=KUEwHdIZ8xN2JMUvUG--T_S_VgREJuvARqNWsnPpKsk,23202
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/__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=Yn8bf0_uA_ukF1Od_yrFIkBYnO0EFq-uvsVrbO2FUrM,9967
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=xh7lqQT-e3PMIisdRIkkfbWP-GrCRZF0KAo03V0ixEI,6401
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=4I0Vjo3xCaYuJktxUGHalEv-eW5h-4fHzgwhjImEuWs,4572
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=ArJ5C8iWJhHNPFkN048DXh6EwQ8LMfnTMwoNlKV9NoI,17191
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=ZYg68FoA1oA6PPXRMx9RyxUCoCh3mKpG7cYTlcO3xKk,18368
69
+ mapillary_tools/serializer/gpx.py,sha256=fwnJ0KF8d8IWRoIGM70NrYDmS89akC3tyRnY3fYc-R8,4344
70
+ mapillary_tools-0.14.0b1.dist-info/licenses/LICENSE,sha256=l2D8cKfFmmJq_wcVq_JElPJrlvWQOzNWx7gMLINucxc,1292
71
+ mapillary_tools-0.14.0b1.dist-info/METADATA,sha256=jz6asGlxY7OFZbmqSHQ1_SxATrzJWMnPsElBl618HkE,22188
72
+ mapillary_tools-0.14.0b1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
73
+ mapillary_tools-0.14.0b1.dist-info/entry_points.txt,sha256=A3f3LP-BO_P-U8Y29QfpT4jx6Mjk3sXjTi2Yew4bvj8,75
74
+ mapillary_tools-0.14.0b1.dist-info/top_level.txt,sha256=FbDkMgOrt1S70ho1WSBrOwzKOSkJFDwwqFOoY5-527s,16
75
+ mapillary_tools-0.14.0b1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,77 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import logging
4
- import typing as T
5
- from pathlib import Path
6
-
7
- from .. import exiftool_read, types, utils
8
- from . import (
9
- geotag_images_from_exiftool,
10
- geotag_images_from_video,
11
- geotag_videos_from_exiftool_video,
12
- )
13
- from .geotag_from_generic import GeotagImagesFromGeneric
14
-
15
-
16
- LOG = logging.getLogger(__name__)
17
-
18
-
19
- class GeotagImagesFromExifToolBothImageAndVideo(GeotagImagesFromGeneric):
20
- def __init__(
21
- self,
22
- image_paths: T.Sequence[Path],
23
- xml_path: Path,
24
- offset_time: float = 0.0,
25
- num_processes: int | None = None,
26
- ):
27
- super().__init__(image_paths, num_processes=num_processes)
28
- self.xml_path = xml_path
29
- self.offset_time = offset_time
30
-
31
- def geotag_samples(self) -> list[types.ImageMetadataOrError]:
32
- # Find all video paths in self.xml_path
33
- rdf_description_by_path = exiftool_read.index_rdf_description_by_path(
34
- [self.xml_path]
35
- )
36
- video_paths = utils.find_videos(
37
- [Path(pathstr) for pathstr in rdf_description_by_path.keys()],
38
- skip_subfolders=True,
39
- )
40
- # Find all video paths that have sample images
41
- samples_by_video = utils.find_all_image_samples(self.image_paths, video_paths)
42
-
43
- video_metadata_or_errors = (
44
- geotag_videos_from_exiftool_video.GeotagVideosFromExifToolVideo(
45
- list(samples_by_video.keys()),
46
- self.xml_path,
47
- num_processes=self.num_processes,
48
- ).to_description()
49
- )
50
- sample_paths = sum(samples_by_video.values(), [])
51
- sample_metadata_or_errors = geotag_images_from_video.GeotagImagesFromVideo(
52
- sample_paths,
53
- video_metadata_or_errors,
54
- offset_time=self.offset_time,
55
- num_processes=self.num_processes,
56
- ).to_description()
57
-
58
- return sample_metadata_or_errors
59
-
60
- def to_description(self) -> list[types.ImageMetadataOrError]:
61
- sample_metadata_or_errors = self.geotag_samples()
62
-
63
- sample_paths = set(metadata.filename for metadata in sample_metadata_or_errors)
64
-
65
- non_sample_paths = [
66
- path for path in self.image_paths if path not in sample_paths
67
- ]
68
-
69
- non_sample_metadata_or_errors = (
70
- geotag_images_from_exiftool.GeotagImagesFromExifTool(
71
- non_sample_paths,
72
- self.xml_path,
73
- num_processes=self.num_processes,
74
- ).to_description()
75
- )
76
-
77
- return sample_metadata_or_errors + non_sample_metadata_or_errors
@@ -1,151 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import logging
4
- import typing as T
5
- import xml.etree.ElementTree as ET
6
- from pathlib import Path
7
-
8
- from .. import constants, exceptions, exiftool_read, geo, types, utils
9
- from ..exiftool_read_video import ExifToolReadVideo
10
- from ..exiftool_runner import ExiftoolRunner
11
- from ..gpmf import gpmf_gps_filter
12
- from ..telemetry import GPSPoint
13
- from .geotag_from_generic import GenericVideoExtractor, GeotagVideosFromGeneric
14
-
15
- LOG = logging.getLogger(__name__)
16
-
17
-
18
- class VideoExifToolExtractor(GenericVideoExtractor):
19
- def __init__(self, video_path: Path, element: ET.Element):
20
- super().__init__(video_path)
21
- self.element = element
22
-
23
- def extract(self) -> types.VideoMetadataOrError:
24
- exif = ExifToolReadVideo(ET.ElementTree(self.element))
25
-
26
- make = exif.extract_make()
27
- model = exif.extract_model()
28
-
29
- is_gopro = make is not None and make.upper() in ["GOPRO"]
30
-
31
- points = exif.extract_gps_track()
32
-
33
- # ExifTool has no idea if GPS is not found or found but empty
34
- if is_gopro:
35
- if not points:
36
- raise exceptions.MapillaryGPXEmptyError("Empty GPS data found")
37
-
38
- # ExifTool (since 13.04) converts GPSSpeed for GoPro to km/h, so here we convert it back to m/s
39
- for p in points:
40
- if isinstance(p, GPSPoint) and p.ground_speed is not None:
41
- p.ground_speed = p.ground_speed / 3.6
42
-
43
- if isinstance(points[0], GPSPoint):
44
- points = T.cast(
45
- T.List[geo.Point],
46
- gpmf_gps_filter.remove_noisy_points(
47
- T.cast(T.List[GPSPoint], points)
48
- ),
49
- )
50
- if not points:
51
- raise exceptions.MapillaryGPSNoiseError("GPS is too noisy")
52
-
53
- if not points:
54
- raise exceptions.MapillaryVideoGPSNotFoundError(
55
- "No GPS data found from the video"
56
- )
57
-
58
- filetype = types.FileType.GOPRO if is_gopro else types.FileType.VIDEO
59
-
60
- video_metadata = types.VideoMetadata(
61
- self.video_path,
62
- filesize=utils.get_file_size(self.video_path),
63
- filetype=filetype,
64
- points=points,
65
- make=make,
66
- model=model,
67
- )
68
-
69
- return video_metadata
70
-
71
-
72
- class GeotagVideosFromExifToolVideo(GeotagVideosFromGeneric):
73
- def __init__(
74
- self,
75
- video_paths: T.Sequence[Path],
76
- xml_path: Path,
77
- num_processes: int | None = None,
78
- ):
79
- super().__init__(video_paths, num_processes=num_processes)
80
- self.xml_path = xml_path
81
-
82
- def _generate_video_extractors(
83
- self,
84
- ) -> T.Sequence[GenericVideoExtractor | types.ErrorMetadata]:
85
- rdf_description_by_path = exiftool_read.index_rdf_description_by_path(
86
- [self.xml_path]
87
- )
88
-
89
- results: list[VideoExifToolExtractor | types.ErrorMetadata] = []
90
-
91
- for path in self.video_paths:
92
- rdf_description = rdf_description_by_path.get(
93
- exiftool_read.canonical_path(path)
94
- )
95
- if rdf_description is None:
96
- exc = exceptions.MapillaryEXIFNotFoundError(
97
- f"The {exiftool_read._DESCRIPTION_TAG} XML element for the video not found"
98
- )
99
- results.append(
100
- types.describe_error_metadata(
101
- exc, path, filetype=types.FileType.VIDEO
102
- )
103
- )
104
- else:
105
- results.append(VideoExifToolExtractor(path, rdf_description))
106
-
107
- return results
108
-
109
-
110
- class GeotagVideosFromExifToolRunner(GeotagVideosFromGeneric):
111
- def _generate_video_extractors(
112
- self,
113
- ) -> T.Sequence[GenericVideoExtractor | types.ErrorMetadata]:
114
- runner = ExiftoolRunner(constants.EXIFTOOL_PATH)
115
-
116
- LOG.debug(
117
- "Extracting XML from %d videos with exiftool command: %s",
118
- len(self.video_paths),
119
- " ".join(runner._build_args_read_stdin()),
120
- )
121
-
122
- try:
123
- xml = runner.extract_xml(self.video_paths)
124
- except FileNotFoundError as ex:
125
- raise exceptions.MapillaryExiftoolNotFoundError(ex) from ex
126
-
127
- rdf_description_by_path = (
128
- exiftool_read.index_rdf_description_by_path_from_xml_element(
129
- ET.fromstring(xml)
130
- )
131
- )
132
-
133
- results: list[VideoExifToolExtractor | types.ErrorMetadata] = []
134
-
135
- for path in self.video_paths:
136
- rdf_description = rdf_description_by_path.get(
137
- exiftool_read.canonical_path(path)
138
- )
139
- if rdf_description is None:
140
- exc = exceptions.MapillaryEXIFNotFoundError(
141
- f"The {exiftool_read._DESCRIPTION_TAG} XML element for the video not found"
142
- )
143
- results.append(
144
- types.describe_error_metadata(
145
- exc, path, filetype=types.FileType.VIDEO
146
- )
147
- )
148
- else:
149
- results.append(VideoExifToolExtractor(path, rdf_description))
150
-
151
- return results
@@ -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]