mapillary-tools 0.14.0a2__tar.gz → 0.14.1__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 (101) hide show
  1. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/PKG-INFO +91 -34
  2. mapillary_tools-0.14.0a2/mapillary_tools.egg-info/PKG-INFO → mapillary_tools-0.14.1/README.md +67 -46
  3. mapillary_tools-0.14.1/mapillary_tools/__init__.py +1 -0
  4. mapillary_tools-0.14.1/mapillary_tools/api_v4.py +176 -0
  5. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/authenticate.py +54 -46
  6. mapillary_tools-0.14.1/mapillary_tools/blackvue_parser.py +195 -0
  7. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/commands/__main__.py +15 -16
  8. mapillary_tools-0.14.1/mapillary_tools/commands/upload.py +88 -0
  9. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/config.py +38 -17
  10. mapillary_tools-0.14.1/mapillary_tools/constants.py +175 -0
  11. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/exceptions.py +4 -0
  12. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/exif_read.py +2 -1
  13. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/exif_write.py +3 -1
  14. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/exiftool_read_video.py +52 -15
  15. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/exiftool_runner.py +4 -24
  16. mapillary_tools-0.14.1/mapillary_tools/ffmpeg.py +626 -0
  17. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/geo.py +16 -0
  18. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/geotag/base.py +8 -4
  19. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/geotag/factory.py +106 -89
  20. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/geotag/geotag_images_from_exiftool.py +27 -20
  21. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/geotag/geotag_images_from_gpx.py +7 -6
  22. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/geotag/geotag_images_from_video.py +35 -0
  23. mapillary_tools-0.14.1/mapillary_tools/geotag/geotag_videos_from_exiftool.py +144 -0
  24. mapillary_tools-0.14.1/mapillary_tools/geotag/geotag_videos_from_gpx.py +52 -0
  25. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/geotag/options.py +25 -3
  26. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/geotag/utils.py +9 -12
  27. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/geotag/video_extractors/base.py +1 -1
  28. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/geotag/video_extractors/exiftool.py +1 -1
  29. mapillary_tools-0.14.1/mapillary_tools/geotag/video_extractors/gpx.py +117 -0
  30. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/geotag/video_extractors/native.py +34 -31
  31. mapillary_tools-0.14.1/mapillary_tools/history.py +182 -0
  32. mapillary_tools-0.14.1/mapillary_tools/http.py +211 -0
  33. mapillary_tools-0.14.1/mapillary_tools/mp4/__init__.py +0 -0
  34. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/mp4/construct_mp4_parser.py +8 -2
  35. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/process_geotag_properties.py +47 -35
  36. mapillary_tools-0.14.1/mapillary_tools/process_sequence_properties.py +714 -0
  37. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/sample_video.py +8 -8
  38. mapillary_tools-0.14.1/mapillary_tools/serializer/description.py +587 -0
  39. mapillary_tools-0.14.1/mapillary_tools/serializer/gpx.py +132 -0
  40. mapillary_tools-0.14.1/mapillary_tools/types.py +208 -0
  41. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/upload.py +327 -352
  42. mapillary_tools-0.14.1/mapillary_tools/upload_api_v4.py +248 -0
  43. mapillary_tools-0.14.1/mapillary_tools/uploader.py +1162 -0
  44. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/utils.py +57 -5
  45. mapillary_tools-0.14.0a2/README.md → mapillary_tools-0.14.1/mapillary_tools.egg-info/PKG-INFO +103 -16
  46. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools.egg-info/SOURCES.txt +5 -5
  47. mapillary_tools-0.14.1/mapillary_tools.egg-info/requires.txt +11 -0
  48. mapillary_tools-0.14.1/pyproject.toml +85 -0
  49. mapillary_tools-0.14.1/setup.cfg +4 -0
  50. mapillary_tools-0.14.0a2/MANIFEST.in +0 -2
  51. mapillary_tools-0.14.0a2/mapillary_tools/__init__.py +0 -1
  52. mapillary_tools-0.14.0a2/mapillary_tools/api_v4.py +0 -372
  53. mapillary_tools-0.14.0a2/mapillary_tools/blackvue_parser.py +0 -138
  54. mapillary_tools-0.14.0a2/mapillary_tools/commands/upload.py +0 -59
  55. mapillary_tools-0.14.0a2/mapillary_tools/constants.py +0 -91
  56. mapillary_tools-0.14.0a2/mapillary_tools/ffmpeg.py +0 -452
  57. mapillary_tools-0.14.0a2/mapillary_tools/geotag/geotag_videos_from_exiftool.py +0 -97
  58. mapillary_tools-0.14.0a2/mapillary_tools/geotag/geotag_videos_from_gpx.py +0 -39
  59. mapillary_tools-0.14.0a2/mapillary_tools/geotag/video_extractors/gpx.py +0 -126
  60. mapillary_tools-0.14.0a2/mapillary_tools/history.py +0 -62
  61. mapillary_tools-0.14.0a2/mapillary_tools/process_sequence_properties.py +0 -699
  62. mapillary_tools-0.14.0a2/mapillary_tools/types.py +0 -774
  63. mapillary_tools-0.14.0a2/mapillary_tools/upload_api_v4.py +0 -195
  64. mapillary_tools-0.14.0a2/mapillary_tools/uploader.py +0 -581
  65. mapillary_tools-0.14.0a2/mapillary_tools.egg-info/requires.txt +0 -10
  66. mapillary_tools-0.14.0a2/requirements.txt +0 -10
  67. mapillary_tools-0.14.0a2/schema/image_description_schema.json +0 -189
  68. mapillary_tools-0.14.0a2/setup.cfg +0 -8
  69. mapillary_tools-0.14.0a2/setup.py +0 -60
  70. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/LICENSE +0 -0
  71. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/camm/camm_builder.py +0 -0
  72. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/camm/camm_parser.py +0 -0
  73. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/commands/__init__.py +0 -0
  74. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/commands/authenticate.py +0 -0
  75. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/commands/process.py +0 -0
  76. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/commands/process_and_upload.py +0 -0
  77. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/commands/sample_video.py +0 -0
  78. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/commands/video_process.py +0 -0
  79. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/commands/video_process_and_upload.py +0 -0
  80. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/commands/zip.py +0 -0
  81. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/exiftool_read.py +0 -0
  82. {mapillary_tools-0.14.0a2/mapillary_tools/mp4 → mapillary_tools-0.14.1/mapillary_tools/geotag}/__init__.py +0 -0
  83. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/geotag/geotag_images_from_exif.py +0 -0
  84. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/geotag/geotag_images_from_gpx_file.py +0 -0
  85. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/geotag/geotag_images_from_nmea_file.py +0 -0
  86. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/geotag/geotag_videos_from_video.py +0 -0
  87. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/geotag/image_extractors/base.py +0 -0
  88. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/geotag/image_extractors/exif.py +0 -0
  89. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/geotag/image_extractors/exiftool.py +0 -0
  90. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/gpmf/gpmf_gps_filter.py +0 -0
  91. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/gpmf/gpmf_parser.py +0 -0
  92. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/gpmf/gps_filter.py +0 -0
  93. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/ipc.py +0 -0
  94. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/mp4/io_utils.py +0 -0
  95. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/mp4/mp4_sample_parser.py +0 -0
  96. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/mp4/simple_mp4_builder.py +0 -0
  97. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/mp4/simple_mp4_parser.py +0 -0
  98. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools/telemetry.py +0 -0
  99. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools.egg-info/dependency_links.txt +0 -0
  100. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools.egg-info/entry_points.txt +0 -0
  101. {mapillary_tools-0.14.0a2 → mapillary_tools-0.14.1}/mapillary_tools.egg-info/top_level.txt +0 -0
@@ -1,32 +1,38 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mapillary_tools
3
- Version: 0.14.0a2
3
+ Version: 0.14.1
4
4
  Summary: Mapillary Image/Video Import Pipeline
5
- Home-page: https://github.com/mapillary/mapillary_tools
6
- Author: Mapillary
5
+ Author-email: Mapillary <support@mapillary.com>
7
6
  License: BSD
8
- Requires-Python: >=3.8
7
+ Project-URL: Homepage, https://github.com/mapillary/mapillary_tools
8
+ Project-URL: Repository, https://github.com/mapillary/mapillary_tools
9
+ Project-URL: Issues, https://github.com/mapillary/mapillary_tools/issues
10
+ Keywords: mapillary,gis,computer vision,street view
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: End Users/Desktop
14
+ Classifier: License :: OSI Approved :: BSD License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Requires-Python: >=3.9
9
22
  Description-Content-Type: text/markdown
10
23
  License-File: LICENSE
11
24
  Requires-Dist: appdirs<2.0.0,>=1.4.4
12
- Requires-Dist: construct<3.0.0,>=2.10.0
13
- Requires-Dist: exifread==2.3.2
14
- Requires-Dist: piexif==1.1.3
15
- Requires-Dist: gpxpy<1.6.0,>=1.5.0
25
+ Requires-Dist: construct~=2.10.0
26
+ Requires-Dist: exifread~=3.0
27
+ Requires-Dist: gpxpy~=1.6.0
28
+ Requires-Dist: humanize>=4.12.3
29
+ Requires-Dist: jsonschema~=4.17.0
30
+ Requires-Dist: piexif~=1.1
16
31
  Requires-Dist: pynmea2<2.0.0,>=1.12.0
17
32
  Requires-Dist: requests[socks]<3.0.0,>=2.20.0
18
33
  Requires-Dist: tqdm<5.0,>=4.0
19
- Requires-Dist: typing_extensions
20
- Requires-Dist: jsonschema~=4.17.3
21
- Dynamic: author
22
- Dynamic: description
23
- Dynamic: description-content-type
24
- Dynamic: home-page
25
- Dynamic: license
34
+ Requires-Dist: typing-extensions>=4.12.2
26
35
  Dynamic: license-file
27
- Dynamic: requires-dist
28
- Dynamic: requires-python
29
- Dynamic: summary
30
36
 
31
37
  <p align="center">
32
38
  <a href="https://github.com/mapillary/mapillary_tools/">
@@ -58,18 +64,40 @@ mapillary_tools --help
58
64
  <!--ts-->
59
65
 
60
66
  - [Supported File Formats](#supported-file-formats)
67
+ - [Image Formats](#image-formats)
68
+ - [Video Formats](#video-formats)
61
69
  - [Installation](#installation)
70
+ - [Standalone Executable](#standalone-executable)
71
+ - [Installing via pip](#installing-via-pip)
72
+ - [Installing on Android Devices](#installing-on-android-devices)
62
73
  - [Usage](#usage)
63
74
  - [Process and Upload](#process-and-upload)
64
75
  - [Process](#process)
65
76
  - [Upload](#upload)
66
77
  - [Advanced Usage](#advanced-usage)
67
78
  - [Local Video Processing](#local-video-processing)
79
+ - [Install FFmpeg](#install-ffmpeg)
80
+ - [Video Processing](#video-processing)
68
81
  - [Geotagging with GPX](#geotagging-with-gpx)
82
+ - [New video geotagging features (experimental)](#new-video-geotagging-features-experimental)
83
+ - [Usage](#usage-1)
84
+ - [Examples](#examples)
85
+ - [Generic supported videos](#generic-supported-videos)
86
+ - [External GPX](#external-gpx)
87
+ - [Insta360 stitched videos](#insta360-stitched-videos)
88
+ - [Limitations of `--video_geotag_source`](#limitations-of---video_geotag_source)
69
89
  - [Authenticate](#authenticate)
90
+ - [Examples](#examples-1)
70
91
  - [Image Description](#image-description)
71
92
  - [Zip Images](#zip-images)
72
93
  - [Development](#development)
94
+ - [Setup](#setup)
95
+ - [Option 1: Using uv (Recommended)](#option-1-using-uv-recommended)
96
+ - [Option 2: Using pip with virtual environment](#option-2-using-pip-with-virtual-environment)
97
+ - [Running the code](#running-the-code)
98
+ - [Tests](#tests)
99
+ - [Code Quality](#code-quality)
100
+ - [Release and Build](#release-and-build)
73
101
 
74
102
  <!--te-->
75
103
 
@@ -495,29 +523,49 @@ git clone git@github.com:mapillary/mapillary_tools.git
495
523
  cd mapillary_tools
496
524
  ```
497
525
 
498
- Set up the virtual environment. It is optional but recommended:
526
+ ### Option 1: Using uv (Recommended)
527
+
528
+ Use [uv](https://docs.astral.sh/uv/) - a fast Python package manager.
529
+
530
+ Install the project in development mode with all dependencies:
499
531
 
500
532
  ```sh
501
- pip install pipenv
533
+ # Install the project and development dependencies
534
+ uv sync --group dev
535
+
536
+ # Activate the virtual environment
537
+ source .venv/bin/activate # On Windows: .venv\Scripts\activate
502
538
  ```
503
539
 
504
- Install dependencies:
540
+ ### Option 2: Using pip with virtual environment
541
+
542
+ Set up a virtual environment (recommended):
505
543
 
506
544
  ```sh
507
- pipenv install -r requirements.txt
508
- pipenv install -r requirements-dev.txt
545
+ python -m venv .venv
546
+ source .venv/bin/activate # On Windows: .venv\Scripts\activate
509
547
  ```
510
548
 
511
- Enter the virtualenv shell:
549
+ Install the project in development mode:
512
550
 
513
551
  ```sh
514
- pipenv shell
552
+ # Install the project and all dependencies in editable mode
553
+ pip install -e .
554
+
555
+ # Install development dependencies
556
+ pip install --group dev
515
557
  ```
516
558
 
559
+ ## Running the code
560
+
517
561
  Run the code from the repository:
518
562
 
519
563
  ```sh
520
- python3 -m mapillary_tools.commands --version
564
+ # If you have mapillary_tools installed in editable mode
565
+ mapillary_tools --version
566
+
567
+ # Alternatively
568
+ python -m mapillary_tools.commands --version
521
569
  ```
522
570
 
523
571
  ## Tests
@@ -525,19 +573,28 @@ python3 -m mapillary_tools.commands --version
525
573
  Run tests:
526
574
 
527
575
  ```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
576
+ # Test all cases
577
+ pytest -s -vv tests
578
+ # Or test a single case specifically
579
+ pytest -s -vv tests/unit/test_camm_parser.py::test_build_and_parse
532
580
  ```
533
581
 
534
- Run linting:
582
+ ## Code Quality
583
+
584
+ Run code formatting and linting:
535
585
 
536
586
  ```sh
537
- # format code
538
- black mapillary_tools tests
539
- # sort imports
587
+ # Format code with ruff
588
+ ruff format mapillary_tools tests
589
+
590
+ # Lint code with ruff
591
+ ruff check mapillary_tools tests
592
+
593
+ # Sort imports with usort
540
594
  usort format mapillary_tools tests
595
+
596
+ # Type checking with mypy
597
+ mypy mapillary_tools
541
598
  ```
542
599
 
543
600
  ## 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.1"
@@ -0,0 +1,176 @@
1
+ from __future__ import annotations
2
+
3
+ import enum
4
+ import logging
5
+ import os
6
+ import typing as T
7
+
8
+ import requests
9
+
10
+ from . import http
11
+
12
+ LOG = logging.getLogger(__name__)
13
+ MAPILLARY_CLIENT_TOKEN = os.getenv(
14
+ "MAPILLARY_CLIENT_TOKEN", "MLY|5675152195860640|6b02c72e6e3c801e5603ab0495623282"
15
+ )
16
+ MAPILLARY_GRAPH_API_ENDPOINT = os.getenv(
17
+ "MAPILLARY_GRAPH_API_ENDPOINT", "https://graph.mapillary.com"
18
+ )
19
+ REQUESTS_TIMEOUT: float = 60 # 1 minutes
20
+
21
+
22
+ class HTTPContentError(Exception):
23
+ """
24
+ Raised when the HTTP response is ok (200) but the content is not as expected
25
+ e.g. not JSON or not a valid response.
26
+ """
27
+
28
+ def __init__(self, message: str, response: requests.Response):
29
+ self.response = response
30
+ super().__init__(message)
31
+
32
+
33
+ class ClusterFileType(enum.Enum):
34
+ ZIP = "zip"
35
+ BLACKVUE = "mly_blackvue_video"
36
+ CAMM = "mly_camm_video"
37
+ MLY_BUNDLE_MANIFEST = "mly_bundle_manifest"
38
+
39
+
40
+ def create_user_session(user_access_token: str) -> requests.Session:
41
+ session = http.Session()
42
+ session.headers["Authorization"] = f"OAuth {user_access_token}"
43
+ return session
44
+
45
+
46
+ def create_client_session(disable_logging: bool = False) -> requests.Session:
47
+ session = http.Session()
48
+ session.headers["Authorization"] = f"OAuth {MAPILLARY_CLIENT_TOKEN}"
49
+ if disable_logging:
50
+ session.disable_logging_request = True
51
+ session.disable_logging_response = True
52
+ return session
53
+
54
+
55
+ def is_auth_error(resp: requests.Response) -> bool:
56
+ if resp.status_code in [401, 403]:
57
+ return True
58
+
59
+ if resp.status_code in [400]:
60
+ try:
61
+ error_body = resp.json()
62
+ except Exception:
63
+ error_body = {}
64
+
65
+ type = error_body.get("debug_info", {}).get("type")
66
+ if type in ["NotAuthorizedError"]:
67
+ return True
68
+
69
+ return False
70
+
71
+
72
+ def extract_auth_error_message(resp: requests.Response) -> str:
73
+ assert is_auth_error(resp), "has to be an auth error"
74
+
75
+ try:
76
+ error_body = resp.json()
77
+ except Exception:
78
+ error_body = {}
79
+
80
+ # from Graph APIs
81
+ message = error_body.get("error", {}).get("message")
82
+ if message is not None:
83
+ return str(message)
84
+
85
+ # from upload service
86
+ message = error_body.get("debug_info", {}).get("message")
87
+ if message is not None:
88
+ return str(message)
89
+
90
+ return resp.text
91
+
92
+
93
+ def get_upload_token(
94
+ client_session: requests.Session, email: str, password: str
95
+ ) -> requests.Response:
96
+ url = f"{MAPILLARY_GRAPH_API_ENDPOINT}/login"
97
+ json_data = {"email": email, "password": password, "locale": "en_US"}
98
+
99
+ resp = client_session.post(url, json=json_data, timeout=REQUESTS_TIMEOUT)
100
+ resp.raise_for_status()
101
+
102
+ return resp
103
+
104
+
105
+ def fetch_organization(
106
+ user_session: requests.Session, organization_id: int | str
107
+ ) -> requests.Response:
108
+ url = f"{MAPILLARY_GRAPH_API_ENDPOINT}/{organization_id}"
109
+ params = {"fields": ",".join(["slug", "description", "name"])}
110
+
111
+ resp = user_session.get(url, params=params, timeout=REQUESTS_TIMEOUT)
112
+ resp.raise_for_status()
113
+
114
+ return resp
115
+
116
+
117
+ def fetch_user_or_me(
118
+ user_session: requests.Session, user_id: int | str | None = None
119
+ ) -> requests.Response:
120
+ if user_id is None:
121
+ url = f"{MAPILLARY_GRAPH_API_ENDPOINT}/me"
122
+ else:
123
+ url = f"{MAPILLARY_GRAPH_API_ENDPOINT}/{user_id}"
124
+ params = {"fields": ",".join(["id", "username"])}
125
+
126
+ resp = user_session.get(url, params=params, timeout=REQUESTS_TIMEOUT)
127
+ resp.raise_for_status()
128
+
129
+ return resp
130
+
131
+
132
+ ActionType = T.Literal[
133
+ "upload_started_upload", "upload_finished_upload", "upload_failed_upload"
134
+ ]
135
+
136
+
137
+ def log_event(
138
+ client_session: requests.Session, action_type: ActionType, properties: dict
139
+ ) -> requests.Response:
140
+ url = f"{MAPILLARY_GRAPH_API_ENDPOINT}/logging"
141
+ json_data = {"action_type": action_type, "properties": properties}
142
+
143
+ resp = client_session.post(url, json=json_data, timeout=REQUESTS_TIMEOUT)
144
+ resp.raise_for_status()
145
+
146
+ return resp
147
+
148
+
149
+ def finish_upload(
150
+ user_session: requests.Session,
151
+ file_handle: str,
152
+ cluster_filetype: ClusterFileType,
153
+ organization_id: int | str | None = None,
154
+ ) -> requests.Response:
155
+ url = f"{MAPILLARY_GRAPH_API_ENDPOINT}/finish_upload"
156
+ json_data: dict[str, str | int] = {
157
+ "file_handle": file_handle,
158
+ "file_type": cluster_filetype.value,
159
+ }
160
+ if organization_id is not None:
161
+ json_data["organization_id"] = organization_id
162
+
163
+ resp = user_session.post(url, json=json_data, timeout=REQUESTS_TIMEOUT)
164
+ resp.raise_for_status()
165
+
166
+ return resp
167
+
168
+
169
+ def jsonify_response(resp: requests.Response) -> T.Any:
170
+ """
171
+ Convert the response to JSON, raising HTTPContentError if the response is not JSON.
172
+ """
173
+ try:
174
+ return resp.json()
175
+ except requests.JSONDecodeError as ex:
176
+ raise HTTPContentError("Invalid JSON response", resp) from ex