mapillary-tools 0.14.0a2__tar.gz → 0.14.0b1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/PKG-INFO +87 -31
- mapillary_tools-0.14.0a2/mapillary_tools.egg-info/PKG-INFO → mapillary_tools-0.14.0b1/README.md +67 -46
- mapillary_tools-0.14.0b1/mapillary_tools/__init__.py +1 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/api_v4.py +1 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/authenticate.py +9 -9
- mapillary_tools-0.14.0b1/mapillary_tools/blackvue_parser.py +195 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/config.py +38 -17
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/constants.py +2 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/exiftool_read_video.py +52 -15
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/exiftool_runner.py +4 -24
- mapillary_tools-0.14.0b1/mapillary_tools/ffmpeg.py +626 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/base.py +2 -2
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/factory.py +97 -88
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/geotag_images_from_exiftool.py +26 -19
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/geotag_images_from_gpx.py +13 -6
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/geotag_images_from_video.py +35 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/geotag_videos_from_exiftool.py +39 -13
- mapillary_tools-0.14.0b1/mapillary_tools/geotag/geotag_videos_from_gpx.py +52 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/options.py +25 -3
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/video_extractors/base.py +1 -1
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/video_extractors/exiftool.py +1 -1
- mapillary_tools-0.14.0b1/mapillary_tools/geotag/video_extractors/gpx.py +116 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/video_extractors/native.py +9 -31
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/history.py +4 -1
- mapillary_tools-0.14.0b1/mapillary_tools/mp4/__init__.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/process_geotag_properties.py +16 -8
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/process_sequence_properties.py +9 -11
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/sample_video.py +7 -6
- mapillary_tools-0.14.0b1/mapillary_tools/serializer/description.py +587 -0
- mapillary_tools-0.14.0b1/mapillary_tools/serializer/gpx.py +132 -0
- mapillary_tools-0.14.0b1/mapillary_tools/types.py +208 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/upload.py +176 -197
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/upload_api_v4.py +94 -51
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/uploader.py +284 -138
- mapillary_tools-0.14.0a2/README.md → mapillary_tools-0.14.0b1/mapillary_tools.egg-info/PKG-INFO +102 -16
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools.egg-info/SOURCES.txt +4 -5
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools.egg-info/requires.txt +3 -3
- mapillary_tools-0.14.0b1/pyproject.toml +84 -0
- mapillary_tools-0.14.0b1/setup.cfg +4 -0
- mapillary_tools-0.14.0a2/MANIFEST.in +0 -2
- mapillary_tools-0.14.0a2/mapillary_tools/__init__.py +0 -1
- mapillary_tools-0.14.0a2/mapillary_tools/blackvue_parser.py +0 -138
- mapillary_tools-0.14.0a2/mapillary_tools/ffmpeg.py +0 -452
- mapillary_tools-0.14.0a2/mapillary_tools/geotag/geotag_videos_from_gpx.py +0 -39
- mapillary_tools-0.14.0a2/mapillary_tools/geotag/video_extractors/gpx.py +0 -126
- mapillary_tools-0.14.0a2/mapillary_tools/types.py +0 -774
- mapillary_tools-0.14.0a2/requirements.txt +0 -10
- mapillary_tools-0.14.0a2/schema/image_description_schema.json +0 -189
- mapillary_tools-0.14.0a2/setup.cfg +0 -8
- mapillary_tools-0.14.0a2/setup.py +0 -60
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/LICENSE +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/camm/camm_builder.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/camm/camm_parser.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/commands/__init__.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/commands/__main__.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/commands/authenticate.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/commands/process.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/commands/process_and_upload.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/commands/sample_video.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/commands/upload.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/commands/video_process.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/commands/video_process_and_upload.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/commands/zip.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/exceptions.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/exif_read.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/exif_write.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/exiftool_read.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geo.py +0 -0
- {mapillary_tools-0.14.0a2/mapillary_tools/mp4 → mapillary_tools-0.14.0b1/mapillary_tools/geotag}/__init__.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/geotag_images_from_exif.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/geotag_images_from_gpx_file.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/geotag_images_from_nmea_file.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/geotag_videos_from_video.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/image_extractors/base.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/image_extractors/exif.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/image_extractors/exiftool.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/utils.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/gpmf/gpmf_gps_filter.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/gpmf/gpmf_parser.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/gpmf/gps_filter.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/ipc.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/mp4/construct_mp4_parser.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/mp4/io_utils.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/mp4/mp4_sample_parser.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/mp4/simple_mp4_builder.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/mp4/simple_mp4_parser.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/telemetry.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/utils.py +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools.egg-info/dependency_links.txt +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools.egg-info/entry_points.txt +0 -0
- {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools.egg-info/top_level.txt +0 -0
|
@@ -1,32 +1,37 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mapillary_tools
|
|
3
|
-
Version: 0.14.
|
|
3
|
+
Version: 0.14.0b1
|
|
4
4
|
Summary: Mapillary Image/Video Import Pipeline
|
|
5
|
-
|
|
6
|
-
Author: Mapillary
|
|
5
|
+
Author-email: Mapillary <support@mapillary.com>
|
|
7
6
|
License: BSD
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
539
|
+
### Option 2: Using pip with virtual environment
|
|
540
|
+
|
|
541
|
+
Set up a virtual environment (recommended):
|
|
505
542
|
|
|
506
543
|
```sh
|
|
507
|
-
|
|
508
|
-
|
|
544
|
+
python -m venv .venv
|
|
545
|
+
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
|
509
546
|
```
|
|
510
547
|
|
|
511
|
-
|
|
548
|
+
Install the project in development mode:
|
|
512
549
|
|
|
513
550
|
```sh
|
|
514
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
529
|
-
|
|
530
|
-
#
|
|
531
|
-
|
|
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
|
-
|
|
581
|
+
## Code Quality
|
|
582
|
+
|
|
583
|
+
Run code formatting and linting:
|
|
535
584
|
|
|
536
585
|
```sh
|
|
537
|
-
#
|
|
538
|
-
|
|
539
|
-
|
|
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
|
mapillary_tools-0.14.0a2/mapillary_tools.egg-info/PKG-INFO → mapillary_tools-0.14.0b1/README.md
RENAMED
|
@@ -1,33 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: mapillary_tools
|
|
3
|
-
Version: 0.14.0a2
|
|
4
|
-
Summary: Mapillary Image/Video Import Pipeline
|
|
5
|
-
Home-page: https://github.com/mapillary/mapillary_tools
|
|
6
|
-
Author: Mapillary
|
|
7
|
-
License: BSD
|
|
8
|
-
Requires-Python: >=3.8
|
|
9
|
-
Description-Content-Type: text/markdown
|
|
10
|
-
License-File: LICENSE
|
|
11
|
-
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
|
|
16
|
-
Requires-Dist: pynmea2<2.0.0,>=1.12.0
|
|
17
|
-
Requires-Dist: requests[socks]<3.0.0,>=2.20.0
|
|
18
|
-
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: license-file
|
|
27
|
-
Dynamic: requires-dist
|
|
28
|
-
Dynamic: requires-python
|
|
29
|
-
Dynamic: summary
|
|
30
|
-
|
|
31
1
|
<p align="center">
|
|
32
2
|
<a href="https://github.com/mapillary/mapillary_tools/">
|
|
33
3
|
<img src="https://raw.githubusercontent.com/mapillary/mapillary_tools/main/docs/images/logo.png">
|
|
@@ -58,18 +28,40 @@ mapillary_tools --help
|
|
|
58
28
|
<!--ts-->
|
|
59
29
|
|
|
60
30
|
- [Supported File Formats](#supported-file-formats)
|
|
31
|
+
- [Image Formats](#image-formats)
|
|
32
|
+
- [Video Formats](#video-formats)
|
|
61
33
|
- [Installation](#installation)
|
|
34
|
+
- [Standalone Executable](#standalone-executable)
|
|
35
|
+
- [Installing via pip](#installing-via-pip)
|
|
36
|
+
- [Installing on Android Devices](#installing-on-android-devices)
|
|
62
37
|
- [Usage](#usage)
|
|
63
38
|
- [Process and Upload](#process-and-upload)
|
|
64
39
|
- [Process](#process)
|
|
65
40
|
- [Upload](#upload)
|
|
66
41
|
- [Advanced Usage](#advanced-usage)
|
|
67
42
|
- [Local Video Processing](#local-video-processing)
|
|
43
|
+
- [Install FFmpeg](#install-ffmpeg)
|
|
44
|
+
- [Video Processing](#video-processing)
|
|
68
45
|
- [Geotagging with GPX](#geotagging-with-gpx)
|
|
46
|
+
- [New video geotagging features (experimental)](#new-video-geotagging-features-experimental)
|
|
47
|
+
- [Usage](#usage-1)
|
|
48
|
+
- [Examples](#examples)
|
|
49
|
+
- [Generic supported videos](#generic-supported-videos)
|
|
50
|
+
- [External GPX](#external-gpx)
|
|
51
|
+
- [Insta360 stitched videos](#insta360-stitched-videos)
|
|
52
|
+
- [Limitations of `--video_geotag_source`](#limitations-of---video_geotag_source)
|
|
69
53
|
- [Authenticate](#authenticate)
|
|
54
|
+
- [Examples](#examples-1)
|
|
70
55
|
- [Image Description](#image-description)
|
|
71
56
|
- [Zip Images](#zip-images)
|
|
72
57
|
- [Development](#development)
|
|
58
|
+
- [Setup](#setup)
|
|
59
|
+
- [Option 1: Using uv (Recommended)](#option-1-using-uv-recommended)
|
|
60
|
+
- [Option 2: Using pip with virtual environment](#option-2-using-pip-with-virtual-environment)
|
|
61
|
+
- [Running the code](#running-the-code)
|
|
62
|
+
- [Tests](#tests)
|
|
63
|
+
- [Code Quality](#code-quality)
|
|
64
|
+
- [Release and Build](#release-and-build)
|
|
73
65
|
|
|
74
66
|
<!--te-->
|
|
75
67
|
|
|
@@ -495,29 +487,49 @@ git clone git@github.com:mapillary/mapillary_tools.git
|
|
|
495
487
|
cd mapillary_tools
|
|
496
488
|
```
|
|
497
489
|
|
|
498
|
-
|
|
490
|
+
### Option 1: Using uv (Recommended)
|
|
491
|
+
|
|
492
|
+
Use [uv](https://docs.astral.sh/uv/) - a fast Python package manager.
|
|
493
|
+
|
|
494
|
+
Install the project in development mode with all dependencies:
|
|
499
495
|
|
|
500
496
|
```sh
|
|
501
|
-
|
|
497
|
+
# Install the project and development dependencies
|
|
498
|
+
uv sync --group dev
|
|
499
|
+
|
|
500
|
+
# Activate the virtual environment
|
|
501
|
+
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
|
502
502
|
```
|
|
503
503
|
|
|
504
|
-
|
|
504
|
+
### Option 2: Using pip with virtual environment
|
|
505
|
+
|
|
506
|
+
Set up a virtual environment (recommended):
|
|
505
507
|
|
|
506
508
|
```sh
|
|
507
|
-
|
|
508
|
-
|
|
509
|
+
python -m venv .venv
|
|
510
|
+
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
|
509
511
|
```
|
|
510
512
|
|
|
511
|
-
|
|
513
|
+
Install the project in development mode:
|
|
512
514
|
|
|
513
515
|
```sh
|
|
514
|
-
|
|
516
|
+
# Install the project and all dependencies in editable mode
|
|
517
|
+
pip install -e .
|
|
518
|
+
|
|
519
|
+
# Install development dependencies
|
|
520
|
+
pip install --group dev
|
|
515
521
|
```
|
|
516
522
|
|
|
523
|
+
## Running the code
|
|
524
|
+
|
|
517
525
|
Run the code from the repository:
|
|
518
526
|
|
|
519
527
|
```sh
|
|
520
|
-
|
|
528
|
+
# If you have mapillary_tools installed in editable mode
|
|
529
|
+
mapillary_tools --version
|
|
530
|
+
|
|
531
|
+
# Alternatively
|
|
532
|
+
python -m mapillary_tools.commands --version
|
|
521
533
|
```
|
|
522
534
|
|
|
523
535
|
## Tests
|
|
@@ -525,19 +537,28 @@ python3 -m mapillary_tools.commands --version
|
|
|
525
537
|
Run tests:
|
|
526
538
|
|
|
527
539
|
```sh
|
|
528
|
-
#
|
|
529
|
-
|
|
530
|
-
#
|
|
531
|
-
|
|
540
|
+
# Test all cases
|
|
541
|
+
pytest -s -vv tests
|
|
542
|
+
# Or test a single case specifically
|
|
543
|
+
pytest -s -vv tests/unit/test_camm_parser.py::test_build_and_parse
|
|
532
544
|
```
|
|
533
545
|
|
|
534
|
-
|
|
546
|
+
## Code Quality
|
|
547
|
+
|
|
548
|
+
Run code formatting and linting:
|
|
535
549
|
|
|
536
550
|
```sh
|
|
537
|
-
#
|
|
538
|
-
|
|
539
|
-
|
|
551
|
+
# Format code with ruff
|
|
552
|
+
ruff format mapillary_tools tests
|
|
553
|
+
|
|
554
|
+
# Lint code with ruff
|
|
555
|
+
ruff check mapillary_tools tests
|
|
556
|
+
|
|
557
|
+
# Sort imports with usort
|
|
540
558
|
usort format mapillary_tools tests
|
|
559
|
+
|
|
560
|
+
# Type checking with mypy
|
|
561
|
+
mypy mapillary_tools
|
|
541
562
|
```
|
|
542
563
|
|
|
543
564
|
## Release and Build
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VERSION = "0.14.0b1"
|
|
@@ -11,7 +11,7 @@ import jsonschema
|
|
|
11
11
|
|
|
12
12
|
import requests
|
|
13
13
|
|
|
14
|
-
from . import api_v4, config, constants, exceptions
|
|
14
|
+
from . import api_v4, config, constants, exceptions
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
LOG = logging.getLogger(__name__)
|
|
@@ -64,7 +64,7 @@ def authenticate(
|
|
|
64
64
|
LOG.info('Creating new profile: "%s"', profile_name)
|
|
65
65
|
|
|
66
66
|
if jwt:
|
|
67
|
-
user_items:
|
|
67
|
+
user_items: config.UserItem = {"user_upload_token": jwt}
|
|
68
68
|
user_items = _verify_user_auth(_validate_profile(user_items))
|
|
69
69
|
else:
|
|
70
70
|
user_items = _prompt_login(
|
|
@@ -89,7 +89,7 @@ def authenticate(
|
|
|
89
89
|
def fetch_user_items(
|
|
90
90
|
user_name: str | None = None,
|
|
91
91
|
organization_key: str | None = None,
|
|
92
|
-
) ->
|
|
92
|
+
) -> config.UserItem:
|
|
93
93
|
"""
|
|
94
94
|
Read user information from the config file,
|
|
95
95
|
or prompt the user to authenticate if the specified profile does not exist
|
|
@@ -155,9 +155,9 @@ def _prompt(message: str) -> str:
|
|
|
155
155
|
return input()
|
|
156
156
|
|
|
157
157
|
|
|
158
|
-
def _validate_profile(user_items:
|
|
158
|
+
def _validate_profile(user_items: config.UserItem) -> config.UserItem:
|
|
159
159
|
try:
|
|
160
|
-
jsonschema.validate(user_items,
|
|
160
|
+
jsonschema.validate(user_items, config.UserItemSchema)
|
|
161
161
|
except jsonschema.ValidationError as ex:
|
|
162
162
|
raise exceptions.MapillaryBadParameterError(
|
|
163
163
|
f"Invalid profile format: {ex.message}"
|
|
@@ -165,7 +165,7 @@ def _validate_profile(user_items: types.UserItem) -> types.UserItem:
|
|
|
165
165
|
return user_items
|
|
166
166
|
|
|
167
167
|
|
|
168
|
-
def _verify_user_auth(user_items:
|
|
168
|
+
def _verify_user_auth(user_items: config.UserItem) -> config.UserItem:
|
|
169
169
|
"""
|
|
170
170
|
Verify that the user access token is valid
|
|
171
171
|
"""
|
|
@@ -205,7 +205,7 @@ def _validate_profile_name(profile_name: str):
|
|
|
205
205
|
)
|
|
206
206
|
|
|
207
207
|
|
|
208
|
-
def _list_all_profiles(profiles: dict[str,
|
|
208
|
+
def _list_all_profiles(profiles: dict[str, config.UserItem]) -> None:
|
|
209
209
|
_echo("Existing Mapillary profiles:")
|
|
210
210
|
|
|
211
211
|
# Header
|
|
@@ -256,7 +256,7 @@ def _is_login_retryable(ex: requests.HTTPError) -> bool:
|
|
|
256
256
|
def _prompt_login(
|
|
257
257
|
user_email: str | None = None,
|
|
258
258
|
user_password: str | None = None,
|
|
259
|
-
) ->
|
|
259
|
+
) -> config.UserItem:
|
|
260
260
|
_enabled = _prompt_enabled()
|
|
261
261
|
|
|
262
262
|
if user_email is None:
|
|
@@ -288,7 +288,7 @@ def _prompt_login(
|
|
|
288
288
|
|
|
289
289
|
data = resp.json()
|
|
290
290
|
|
|
291
|
-
user_items:
|
|
291
|
+
user_items: config.UserItem = {
|
|
292
292
|
"user_upload_token": str(data["access_token"]),
|
|
293
293
|
"MAPSettingsUserKey": str(data["user_id"]),
|
|
294
294
|
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import re
|
|
8
|
+
import typing as T
|
|
9
|
+
|
|
10
|
+
import pynmea2
|
|
11
|
+
|
|
12
|
+
from . import geo
|
|
13
|
+
from .mp4 import simple_mp4_parser as sparser
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
LOG = logging.getLogger(__name__)
|
|
17
|
+
NMEA_LINE_REGEX = re.compile(
|
|
18
|
+
rb"""
|
|
19
|
+
^\s*
|
|
20
|
+
\[(\d+)\] # Timestamp
|
|
21
|
+
\s*
|
|
22
|
+
(\$\w{5}.*) # NMEA message
|
|
23
|
+
\s*
|
|
24
|
+
(\[\d+\])? # Strange timestamp
|
|
25
|
+
\s*$
|
|
26
|
+
""",
|
|
27
|
+
re.X,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclasses.dataclass
|
|
32
|
+
class BlackVueInfo:
|
|
33
|
+
# None and [] are equivalent here. Use None as default because:
|
|
34
|
+
# ValueError: mutable default <class 'list'> for field gps is not allowed: use default_factory
|
|
35
|
+
gps: list[geo.Point] | None = None
|
|
36
|
+
make: str = "BlackVue"
|
|
37
|
+
model: str = ""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def extract_blackvue_info(fp: T.BinaryIO) -> BlackVueInfo | None:
|
|
41
|
+
try:
|
|
42
|
+
gps_data = sparser.parse_mp4_data_first(fp, [b"free", b"gps "])
|
|
43
|
+
except sparser.ParsingError:
|
|
44
|
+
gps_data = None
|
|
45
|
+
|
|
46
|
+
if gps_data is None:
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
points = _parse_gps_box(gps_data)
|
|
50
|
+
points.sort(key=lambda p: p.time)
|
|
51
|
+
|
|
52
|
+
if points:
|
|
53
|
+
first_point_time = points[0].time
|
|
54
|
+
for p in points:
|
|
55
|
+
p.time = (p.time - first_point_time) / 1000
|
|
56
|
+
|
|
57
|
+
# Camera model
|
|
58
|
+
try:
|
|
59
|
+
cprt_bytes = sparser.parse_mp4_data_first(fp, [b"free", b"cprt"])
|
|
60
|
+
except sparser.ParsingError:
|
|
61
|
+
cprt_bytes = None
|
|
62
|
+
model = ""
|
|
63
|
+
|
|
64
|
+
if cprt_bytes is None:
|
|
65
|
+
model = ""
|
|
66
|
+
else:
|
|
67
|
+
model = _extract_camera_model_from_cprt(cprt_bytes)
|
|
68
|
+
|
|
69
|
+
return BlackVueInfo(model=model, gps=points)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def extract_camera_model(fp: T.BinaryIO) -> str:
|
|
73
|
+
try:
|
|
74
|
+
cprt_bytes = sparser.parse_mp4_data_first(fp, [b"free", b"cprt"])
|
|
75
|
+
except sparser.ParsingError:
|
|
76
|
+
return ""
|
|
77
|
+
|
|
78
|
+
if cprt_bytes is None:
|
|
79
|
+
return ""
|
|
80
|
+
|
|
81
|
+
return _extract_camera_model_from_cprt(cprt_bytes)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _extract_camera_model_from_cprt(cprt_bytes: bytes) -> str:
|
|
85
|
+
"""
|
|
86
|
+
>>> _extract_camera_model_from_cprt(b' {"model":"DR900X Plus","ver":0.918,"lang":"English","direct":1,"psn":"","temp":34,"GPS":1}')
|
|
87
|
+
'DR900X Plus'
|
|
88
|
+
>>> _extract_camera_model_from_cprt(b' Pittasoft Co., Ltd.;DR900S-1CH;1.008;English;1;D90SS1HAE00661;T69;')
|
|
89
|
+
'DR900S-1CH'
|
|
90
|
+
"""
|
|
91
|
+
cprt_bytes = cprt_bytes.strip().strip(b"\x00")
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
cprt_str = cprt_bytes.decode("utf8")
|
|
95
|
+
except UnicodeDecodeError:
|
|
96
|
+
return ""
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
cprt_json = json.loads(cprt_str)
|
|
100
|
+
except json.JSONDecodeError:
|
|
101
|
+
cprt_json = None
|
|
102
|
+
|
|
103
|
+
if cprt_json is not None:
|
|
104
|
+
return str(cprt_json.get("model", "")).strip()
|
|
105
|
+
|
|
106
|
+
fields = cprt_str.split(";")
|
|
107
|
+
if 2 <= len(fields):
|
|
108
|
+
model = fields[1]
|
|
109
|
+
if model:
|
|
110
|
+
return model.strip()
|
|
111
|
+
else:
|
|
112
|
+
return ""
|
|
113
|
+
else:
|
|
114
|
+
return ""
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _parse_gps_box(gps_data: bytes) -> list[geo.Point]:
|
|
118
|
+
"""
|
|
119
|
+
>>> list(_parse_gps_box(b"[1623057074211]$GPGGA,202530.00,5109.0262,N,11401.8407,W,5,40,0.5,1097.36,M,-17.00,M,18,TSTR*61"))
|
|
120
|
+
[Point(time=1623057074211, lat=51.150436666666664, lon=-114.03067833333333, alt=1097.36, angle=None)]
|
|
121
|
+
|
|
122
|
+
>>> list(_parse_gps_box(b"[1629874404069]$GNGGA,175322.00,3244.53126,N,11710.97811,W,1,12,0.84,17.4,M,-34.0,M,,*45"))
|
|
123
|
+
[Point(time=1629874404069, lat=32.742187666666666, lon=-117.1829685, alt=17.4, angle=None)]
|
|
124
|
+
|
|
125
|
+
>>> list(_parse_gps_box(b"[1629874404069]$GNGLL,4404.14012,N,12118.85993,W,001037.00,A,A*67"))
|
|
126
|
+
[Point(time=1629874404069, lat=44.069002, lon=-121.31433216666667, alt=None, angle=None)]
|
|
127
|
+
|
|
128
|
+
>>> list(_parse_gps_box(b"[1629874404069]$GNRMC,001031.00,A,4404.13993,N,12118.86023,W,0.146,,100117,,,A*7B"))
|
|
129
|
+
[Point(time=1629874404069, lat=44.06899883333333, lon=-121.31433716666666, alt=None, angle=None)]
|
|
130
|
+
|
|
131
|
+
>>> list(_parse_gps_box(b"[1623057074211]$GPVTG,,T,,M,0.078,N,0.144,K,D*28[1623057075215]"))
|
|
132
|
+
[]
|
|
133
|
+
"""
|
|
134
|
+
points_by_sentence_type: dict[str, list[geo.Point]] = {}
|
|
135
|
+
|
|
136
|
+
for line_bytes in gps_data.splitlines():
|
|
137
|
+
match = NMEA_LINE_REGEX.match(line_bytes)
|
|
138
|
+
if match is None:
|
|
139
|
+
continue
|
|
140
|
+
nmea_line_bytes = match.group(2)
|
|
141
|
+
|
|
142
|
+
if not nmea_line_bytes:
|
|
143
|
+
continue
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
nmea_line = nmea_line_bytes.decode("utf8")
|
|
147
|
+
except UnicodeDecodeError:
|
|
148
|
+
continue
|
|
149
|
+
|
|
150
|
+
if not nmea_line:
|
|
151
|
+
continue
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
message = pynmea2.parse(nmea_line)
|
|
155
|
+
except pynmea2.nmea.ParseError:
|
|
156
|
+
continue
|
|
157
|
+
|
|
158
|
+
epoch_ms = int(match.group(1))
|
|
159
|
+
|
|
160
|
+
# https://tavotech.com/gps-nmea-sentence-structure/
|
|
161
|
+
if message.sentence_type in ["GGA"]:
|
|
162
|
+
if not message.is_valid:
|
|
163
|
+
continue
|
|
164
|
+
point = geo.Point(
|
|
165
|
+
time=epoch_ms,
|
|
166
|
+
lat=message.latitude,
|
|
167
|
+
lon=message.longitude,
|
|
168
|
+
alt=message.altitude,
|
|
169
|
+
angle=None,
|
|
170
|
+
)
|
|
171
|
+
points_by_sentence_type.setdefault(message.sentence_type, []).append(point)
|
|
172
|
+
|
|
173
|
+
elif message.sentence_type in ["RMC", "GLL"]:
|
|
174
|
+
if not message.is_valid:
|
|
175
|
+
continue
|
|
176
|
+
point = geo.Point(
|
|
177
|
+
time=epoch_ms,
|
|
178
|
+
lat=message.latitude,
|
|
179
|
+
lon=message.longitude,
|
|
180
|
+
alt=None,
|
|
181
|
+
angle=None,
|
|
182
|
+
)
|
|
183
|
+
points_by_sentence_type.setdefault(message.sentence_type, []).append(point)
|
|
184
|
+
|
|
185
|
+
# This is the extraction order in exiftool
|
|
186
|
+
if "RMC" in points_by_sentence_type:
|
|
187
|
+
return points_by_sentence_type["RMC"]
|
|
188
|
+
|
|
189
|
+
if "GGA" in points_by_sentence_type:
|
|
190
|
+
return points_by_sentence_type["GGA"]
|
|
191
|
+
|
|
192
|
+
if "GLL" in points_by_sentence_type:
|
|
193
|
+
return points_by_sentence_type["GLL"]
|
|
194
|
+
|
|
195
|
+
return []
|