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.
Files changed (91) hide show
  1. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/PKG-INFO +87 -31
  2. mapillary_tools-0.14.0a2/mapillary_tools.egg-info/PKG-INFO → mapillary_tools-0.14.0b1/README.md +67 -46
  3. mapillary_tools-0.14.0b1/mapillary_tools/__init__.py +1 -0
  4. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/api_v4.py +1 -0
  5. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/authenticate.py +9 -9
  6. mapillary_tools-0.14.0b1/mapillary_tools/blackvue_parser.py +195 -0
  7. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/config.py +38 -17
  8. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/constants.py +2 -0
  9. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/exiftool_read_video.py +52 -15
  10. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/exiftool_runner.py +4 -24
  11. mapillary_tools-0.14.0b1/mapillary_tools/ffmpeg.py +626 -0
  12. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/base.py +2 -2
  13. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/factory.py +97 -88
  14. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/geotag_images_from_exiftool.py +26 -19
  15. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/geotag_images_from_gpx.py +13 -6
  16. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/geotag_images_from_video.py +35 -0
  17. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/geotag_videos_from_exiftool.py +39 -13
  18. mapillary_tools-0.14.0b1/mapillary_tools/geotag/geotag_videos_from_gpx.py +52 -0
  19. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/options.py +25 -3
  20. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/video_extractors/base.py +1 -1
  21. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/video_extractors/exiftool.py +1 -1
  22. mapillary_tools-0.14.0b1/mapillary_tools/geotag/video_extractors/gpx.py +116 -0
  23. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/video_extractors/native.py +9 -31
  24. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/history.py +4 -1
  25. mapillary_tools-0.14.0b1/mapillary_tools/mp4/__init__.py +0 -0
  26. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/process_geotag_properties.py +16 -8
  27. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/process_sequence_properties.py +9 -11
  28. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/sample_video.py +7 -6
  29. mapillary_tools-0.14.0b1/mapillary_tools/serializer/description.py +587 -0
  30. mapillary_tools-0.14.0b1/mapillary_tools/serializer/gpx.py +132 -0
  31. mapillary_tools-0.14.0b1/mapillary_tools/types.py +208 -0
  32. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/upload.py +176 -197
  33. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/upload_api_v4.py +94 -51
  34. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/uploader.py +284 -138
  35. mapillary_tools-0.14.0a2/README.md → mapillary_tools-0.14.0b1/mapillary_tools.egg-info/PKG-INFO +102 -16
  36. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools.egg-info/SOURCES.txt +4 -5
  37. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools.egg-info/requires.txt +3 -3
  38. mapillary_tools-0.14.0b1/pyproject.toml +84 -0
  39. mapillary_tools-0.14.0b1/setup.cfg +4 -0
  40. mapillary_tools-0.14.0a2/MANIFEST.in +0 -2
  41. mapillary_tools-0.14.0a2/mapillary_tools/__init__.py +0 -1
  42. mapillary_tools-0.14.0a2/mapillary_tools/blackvue_parser.py +0 -138
  43. mapillary_tools-0.14.0a2/mapillary_tools/ffmpeg.py +0 -452
  44. mapillary_tools-0.14.0a2/mapillary_tools/geotag/geotag_videos_from_gpx.py +0 -39
  45. mapillary_tools-0.14.0a2/mapillary_tools/geotag/video_extractors/gpx.py +0 -126
  46. mapillary_tools-0.14.0a2/mapillary_tools/types.py +0 -774
  47. mapillary_tools-0.14.0a2/requirements.txt +0 -10
  48. mapillary_tools-0.14.0a2/schema/image_description_schema.json +0 -189
  49. mapillary_tools-0.14.0a2/setup.cfg +0 -8
  50. mapillary_tools-0.14.0a2/setup.py +0 -60
  51. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/LICENSE +0 -0
  52. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/camm/camm_builder.py +0 -0
  53. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/camm/camm_parser.py +0 -0
  54. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/commands/__init__.py +0 -0
  55. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/commands/__main__.py +0 -0
  56. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/commands/authenticate.py +0 -0
  57. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/commands/process.py +0 -0
  58. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/commands/process_and_upload.py +0 -0
  59. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/commands/sample_video.py +0 -0
  60. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/commands/upload.py +0 -0
  61. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/commands/video_process.py +0 -0
  62. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/commands/video_process_and_upload.py +0 -0
  63. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/commands/zip.py +0 -0
  64. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/exceptions.py +0 -0
  65. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/exif_read.py +0 -0
  66. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/exif_write.py +0 -0
  67. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/exiftool_read.py +0 -0
  68. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geo.py +0 -0
  69. {mapillary_tools-0.14.0a2/mapillary_tools/mp4 → mapillary_tools-0.14.0b1/mapillary_tools/geotag}/__init__.py +0 -0
  70. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/geotag_images_from_exif.py +0 -0
  71. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/geotag_images_from_gpx_file.py +0 -0
  72. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/geotag_images_from_nmea_file.py +0 -0
  73. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/geotag_videos_from_video.py +0 -0
  74. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/image_extractors/base.py +0 -0
  75. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/image_extractors/exif.py +0 -0
  76. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/image_extractors/exiftool.py +0 -0
  77. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/geotag/utils.py +0 -0
  78. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/gpmf/gpmf_gps_filter.py +0 -0
  79. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/gpmf/gpmf_parser.py +0 -0
  80. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/gpmf/gps_filter.py +0 -0
  81. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/ipc.py +0 -0
  82. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/mp4/construct_mp4_parser.py +0 -0
  83. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/mp4/io_utils.py +0 -0
  84. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/mp4/mp4_sample_parser.py +0 -0
  85. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/mp4/simple_mp4_builder.py +0 -0
  86. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/mp4/simple_mp4_parser.py +0 -0
  87. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/telemetry.py +0 -0
  88. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools/utils.py +0 -0
  89. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools.egg-info/dependency_links.txt +0 -0
  90. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.0b1}/mapillary_tools.egg-info/entry_points.txt +0 -0
  91. {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.0a2
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
@@ -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
- Set up the virtual environment. It is optional but recommended:
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
- pip install pipenv
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
- Install dependencies:
504
+ ### Option 2: Using pip with virtual environment
505
+
506
+ Set up a virtual environment (recommended):
505
507
 
506
508
  ```sh
507
- pipenv install -r requirements.txt
508
- pipenv install -r requirements-dev.txt
509
+ python -m venv .venv
510
+ source .venv/bin/activate # On Windows: .venv\Scripts\activate
509
511
  ```
510
512
 
511
- Enter the virtualenv shell:
513
+ Install the project in development mode:
512
514
 
513
515
  ```sh
514
- pipenv shell
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
- python3 -m mapillary_tools.commands --version
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
- # 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
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
- Run linting:
546
+ ## Code Quality
547
+
548
+ Run code formatting and linting:
535
549
 
536
550
  ```sh
537
- # format code
538
- black mapillary_tools tests
539
- # sort imports
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"
@@ -25,6 +25,7 @@ class ClusterFileType(enum.Enum):
25
25
  ZIP = "zip"
26
26
  BLACKVUE = "mly_blackvue_video"
27
27
  CAMM = "mly_camm_video"
28
+ MLY_BUNDLE_MANIFEST = "mly_bundle_manifest"
28
29
 
29
30
 
30
31
  class HTTPSystemCertsAdapter(HTTPAdapter):
@@ -11,7 +11,7 @@ import jsonschema
11
11
 
12
12
  import requests
13
13
 
14
- from . import api_v4, config, constants, exceptions, types
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: types.UserItem = {"user_upload_token": jwt}
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
- ) -> types.UserItem:
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: types.UserItem) -> types.UserItem:
158
+ def _validate_profile(user_items: config.UserItem) -> config.UserItem:
159
159
  try:
160
- jsonschema.validate(user_items, types.UserItemSchema)
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: types.UserItem) -> types.UserItem:
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, types.UserItem]) -> None:
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
- ) -> types.UserItem:
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: types.UserItem = {
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 []