mapillary-tools 0.10.6a1__tar.gz → 0.11.0b2__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 (86) hide show
  1. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/PKG-INFO +100 -2
  2. mapillary_tools-0.10.6a1/mapillary_tools.egg-info/PKG-INFO → mapillary_tools-0.11.0b2/README.md +87 -11
  3. mapillary_tools-0.11.0b2/mapillary_tools/__init__.py +1 -0
  4. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/api_v4.py +2 -9
  5. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/commands/__main__.py +5 -3
  6. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/commands/process.py +9 -1
  7. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/constants.py +1 -0
  8. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/exceptions.py +4 -0
  9. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/exif_read.py +46 -10
  10. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/exiftool_read.py +4 -0
  11. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/ffmpeg.py +3 -8
  12. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/geotag/construct_mp4_parser.py +2 -8
  13. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/geotag/gpmf_parser.py +1 -7
  14. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/process_geotag_properties.py +115 -51
  15. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/process_sequence_properties.py +3 -3
  16. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/types.py +1 -6
  17. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/uploader.py +2 -9
  18. mapillary_tools-0.11.0b2/mapillary_tools/video_data_extraction/cli_options.py +22 -0
  19. mapillary_tools-0.11.0b2/mapillary_tools/video_data_extraction/extract_video_data.py +190 -0
  20. mapillary_tools-0.11.0b2/mapillary_tools/video_data_extraction/extractors/base_parser.py +73 -0
  21. mapillary_tools-0.11.0b2/mapillary_tools/video_data_extraction/extractors/blackvue_parser.py +33 -0
  22. mapillary_tools-0.11.0b2/mapillary_tools/video_data_extraction/extractors/camm_parser.py +41 -0
  23. mapillary_tools-0.11.0b2/mapillary_tools/video_data_extraction/extractors/exiftool_runtime_parser.py +56 -0
  24. mapillary_tools-0.11.0b2/mapillary_tools/video_data_extraction/extractors/exiftool_xml_parser.py +55 -0
  25. mapillary_tools-0.11.0b2/mapillary_tools/video_data_extraction/extractors/generic_video_parser.py +57 -0
  26. mapillary_tools-0.11.0b2/mapillary_tools/video_data_extraction/extractors/gopro_parser.py +36 -0
  27. mapillary_tools-0.11.0b2/mapillary_tools/video_data_extraction/extractors/gpx_parser.py +29 -0
  28. mapillary_tools-0.11.0b2/mapillary_tools/video_data_extraction/extractors/nmea_parser.py +24 -0
  29. mapillary_tools-0.11.0b2/mapillary_tools/video_data_extraction/video_data_parser_factory.py +46 -0
  30. mapillary_tools-0.10.6a1/README.md → mapillary_tools-0.11.0b2/mapillary_tools.egg-info/PKG-INFO +109 -0
  31. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools.egg-info/SOURCES.txt +12 -0
  32. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/setup.py +8 -2
  33. mapillary_tools-0.10.6a1/mapillary_tools/__init__.py +0 -1
  34. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/LICENSE +0 -0
  35. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/MANIFEST.in +0 -0
  36. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/authenticate.py +0 -0
  37. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/commands/__init__.py +0 -0
  38. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/commands/authenticate.py +0 -0
  39. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/commands/process_and_upload.py +0 -0
  40. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/commands/sample_video.py +0 -0
  41. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/commands/upload.py +0 -0
  42. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/commands/upload_blackvue.py +0 -0
  43. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/commands/upload_camm.py +0 -0
  44. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/commands/upload_zip.py +0 -0
  45. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/commands/video_process.py +0 -0
  46. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/commands/video_process_and_upload.py +0 -0
  47. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/commands/zip.py +0 -0
  48. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/config.py +0 -0
  49. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/exif_write.py +0 -0
  50. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/exiftool_read_video.py +0 -0
  51. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/geo.py +0 -0
  52. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/geotag/__init__.py +0 -0
  53. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/geotag/blackvue_parser.py +0 -0
  54. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/geotag/camm_builder.py +0 -0
  55. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/geotag/camm_parser.py +0 -0
  56. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/geotag/geotag_from_generic.py +0 -0
  57. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/geotag/geotag_images_from_exif.py +0 -0
  58. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/geotag/geotag_images_from_exiftool.py +0 -0
  59. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/geotag/geotag_images_from_exiftool_both_image_and_video.py +0 -0
  60. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/geotag/geotag_images_from_gpx.py +0 -0
  61. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/geotag/geotag_images_from_gpx_file.py +0 -0
  62. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/geotag/geotag_images_from_nmea_file.py +0 -0
  63. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/geotag/geotag_images_from_video.py +0 -0
  64. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/geotag/geotag_videos_from_exiftool_video.py +0 -0
  65. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/geotag/geotag_videos_from_video.py +0 -0
  66. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/geotag/gpmf_gps_filter.py +0 -0
  67. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/geotag/gps_filter.py +0 -0
  68. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/geotag/io_utils.py +0 -0
  69. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/geotag/mp4_sample_parser.py +0 -0
  70. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/geotag/simple_mp4_builder.py +0 -0
  71. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/geotag/simple_mp4_parser.py +0 -0
  72. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/geotag/utils.py +0 -0
  73. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/history.py +0 -0
  74. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/ipc.py +0 -0
  75. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/process_import_meta_properties.py +0 -0
  76. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/sample_video.py +0 -0
  77. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/upload.py +0 -0
  78. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/upload_api_v4.py +0 -0
  79. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools/utils.py +0 -0
  80. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools.egg-info/dependency_links.txt +0 -0
  81. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools.egg-info/entry_points.txt +0 -0
  82. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools.egg-info/requires.txt +0 -0
  83. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/mapillary_tools.egg-info/top_level.txt +0 -0
  84. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/requirements.txt +0 -0
  85. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/schema/image_description_schema.json +0 -0
  86. {mapillary_tools-0.10.6a1 → mapillary_tools-0.11.0b2}/setup.cfg +0 -0
@@ -1,13 +1,24 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mapillary_tools
3
- Version: 0.10.6a1
3
+ Version: 0.11.0b2
4
4
  Summary: Mapillary Image/Video Import Pipeline
5
5
  Home-page: https://github.com/mapillary/mapillary_tools
6
6
  Author: Mapillary
7
7
  License: BSD
8
- Requires-Python: >=3.6
8
+ Requires-Python: >=3.8
9
9
  Description-Content-Type: text/markdown
10
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==1.12.0
17
+ Requires-Dist: requests<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
+ Requires-Dist: dataclasses; python_version <= "3.6"
11
22
 
12
23
  <p align="center">
13
24
  <a href="https://github.com/mapillary/mapillary_tools/">
@@ -303,6 +314,93 @@ mapillary_tools video_process MY_VIDEO_DIR \
303
314
  --video_sample_distance -1 --video_sample_interval 2
304
315
  ```
305
316
 
317
+ ## New video geotagging features (experimental)
318
+
319
+ As experimental features, mapillary_tools can now:
320
+ * Geotag videos from tracks recorded in GPX and NMEA files
321
+ * Invoke `exiftool` internally, if available on the system. `exiftool` can extract geolocation data from a wide
322
+ range of video formats, allowing us to support more cameras with less trouble for the end user.
323
+ * Try several geotagging sources sequentially, until proper data is found.
324
+
325
+ These features apply to the `process` command, which analyzes the video for direct upload instead of sampling it
326
+ into images. They are experimental and will be subject to change in future releases.
327
+
328
+ ### Usage
329
+
330
+ The new video processing is triggered with the `--video_geotag_source SOURCE` option. It can be specified multiple times:
331
+ In this case, each source will be tried in turn, until one returns good quality data.
332
+
333
+ `SOURCE` can be:
334
+ 1. the plain name of the source - one of `video, camm, gopro, blackvue, gpx, nmea, exiftool_xml, exiftool_runtime`
335
+ 2. a JSON object that includes optional parameters
336
+ ```json5
337
+ {
338
+ "source": "SOURCE",
339
+ "pattern": "FILENAME_PATTERN" // optional
340
+ }
341
+ ```
342
+
343
+ PATTERN specifies how the data source file is named, starting from the video filename:
344
+ * `%f`: the full video filename
345
+ * `%g`: the video filename without extension
346
+ * `%e`: the video filename extension
347
+
348
+ Supported sources and their default pattern are:
349
+
350
+ * `video`: parse the video, in order, as `camm, gopro, blackvue`. Pattern: `%f`
351
+ * `camm`: parse the video looking for a CAMM track. Pattern: `%f`
352
+ * `gopro`: parse the video looking for geolocation in GoPro format. Pattern: `%f`
353
+ * `blackvue`: parse the video looking for geolocation in BlackVue format. Pattern: `%f`
354
+ * `gpx`: external GPX file. Pattern: `%g.gpx`
355
+ * `nmea`: external NMEA file. Pattern: `%g.nmea`
356
+ * `exiftool_xml`: external XML file generated by exiftool. Pattern: `%g.xml`
357
+ * `exiftool_runtime`: execute exiftool on the video file. Pattern: `%f`
358
+
359
+ Notes:
360
+ * `exiftool_runtime` only works if exiftool is installed on the system. You can find it at https://exiftool.org/ or through
361
+ your software manager. If exiftool is installed, but is not in the default execution path, it is
362
+ possible to specify its location by setting the environment variable `MAPILLARY_TOOLS_EXIFTOOL_PATH`.
363
+ * Pattern are case-sensitive or not depending on the filesystem - in Windows, `%g.gpx` will match both `basename.gpx`
364
+ and `basename.GPX`, in MacOs, Linux or other Unix systems no.
365
+ * If both `--video_geotag_source` and `--geotag_source` are specified, `--video_geotag_source` will apply to video files
366
+ and `--geotag_source` to image files.
367
+
368
+ ### Examples
369
+
370
+ #### Generic supported videos
371
+
372
+ Process all videos in a directory, trying to parse them as CAMM, GoPro or BlackVue:
373
+ ```sh
374
+ mapillary_tools process --video_geotag_source video VIDEO_DIR/
375
+ ```
376
+
377
+ #### External GPX
378
+
379
+ Process all videos in a directory, taking geolocation data from GPX files. A video named `foo.mp4` will be associated
380
+ with a GPX file called `foo.gpx`.
381
+ ```sh
382
+ mapillary_tools process --video_geotag_source gpx VIDEO_DIR/
383
+ ```
384
+
385
+ #### Insta360 stitched videos
386
+
387
+ The videos to process have been stitched by Insta360 Studio; the geolocation data is in the original
388
+ videos in the parent directory, and there may be GPX files alongside the stitched video.
389
+ First look for GPX, then fallback to running exiftool against the original videos.
390
+ ```sh
391
+ mapillary_tools process \
392
+ --video_geotag_source gpx \
393
+ --video_geotag_source '{"source": "exiftool_runtime", "pattern": "../%g.insv"}' \
394
+ VIDEO_DIR/
395
+ ```
396
+
397
+ ### Limitations of `--video_geotag_source`
398
+
399
+ **External geolocation sources will be aligned with the start of video, there is
400
+ currently no way of applying offsets or scaling the time.** This means, for instance, that GPX tracks must begin precisely
401
+ at the instant of the video, and that timelapse videos are supported only for sources `camm, gopro, blackvue`.
402
+
403
+
306
404
  ## Authenticate
307
405
 
308
406
  The command `authenticate` will update the user credentials stored in the config file.
@@ -1,14 +1,3 @@
1
- Metadata-Version: 2.1
2
- Name: mapillary-tools
3
- Version: 0.10.6a1
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.6
9
- Description-Content-Type: text/markdown
10
- License-File: LICENSE
11
-
12
1
  <p align="center">
13
2
  <a href="https://github.com/mapillary/mapillary_tools/">
14
3
  <img src="https://raw.githubusercontent.com/mapillary/mapillary_tools/main/docs/images/logo.png">
@@ -303,6 +292,93 @@ mapillary_tools video_process MY_VIDEO_DIR \
303
292
  --video_sample_distance -1 --video_sample_interval 2
304
293
  ```
305
294
 
295
+ ## New video geotagging features (experimental)
296
+
297
+ As experimental features, mapillary_tools can now:
298
+ * Geotag videos from tracks recorded in GPX and NMEA files
299
+ * Invoke `exiftool` internally, if available on the system. `exiftool` can extract geolocation data from a wide
300
+ range of video formats, allowing us to support more cameras with less trouble for the end user.
301
+ * Try several geotagging sources sequentially, until proper data is found.
302
+
303
+ These features apply to the `process` command, which analyzes the video for direct upload instead of sampling it
304
+ into images. They are experimental and will be subject to change in future releases.
305
+
306
+ ### Usage
307
+
308
+ The new video processing is triggered with the `--video_geotag_source SOURCE` option. It can be specified multiple times:
309
+ In this case, each source will be tried in turn, until one returns good quality data.
310
+
311
+ `SOURCE` can be:
312
+ 1. the plain name of the source - one of `video, camm, gopro, blackvue, gpx, nmea, exiftool_xml, exiftool_runtime`
313
+ 2. a JSON object that includes optional parameters
314
+ ```json5
315
+ {
316
+ "source": "SOURCE",
317
+ "pattern": "FILENAME_PATTERN" // optional
318
+ }
319
+ ```
320
+
321
+ PATTERN specifies how the data source file is named, starting from the video filename:
322
+ * `%f`: the full video filename
323
+ * `%g`: the video filename without extension
324
+ * `%e`: the video filename extension
325
+
326
+ Supported sources and their default pattern are:
327
+
328
+ * `video`: parse the video, in order, as `camm, gopro, blackvue`. Pattern: `%f`
329
+ * `camm`: parse the video looking for a CAMM track. Pattern: `%f`
330
+ * `gopro`: parse the video looking for geolocation in GoPro format. Pattern: `%f`
331
+ * `blackvue`: parse the video looking for geolocation in BlackVue format. Pattern: `%f`
332
+ * `gpx`: external GPX file. Pattern: `%g.gpx`
333
+ * `nmea`: external NMEA file. Pattern: `%g.nmea`
334
+ * `exiftool_xml`: external XML file generated by exiftool. Pattern: `%g.xml`
335
+ * `exiftool_runtime`: execute exiftool on the video file. Pattern: `%f`
336
+
337
+ Notes:
338
+ * `exiftool_runtime` only works if exiftool is installed on the system. You can find it at https://exiftool.org/ or through
339
+ your software manager. If exiftool is installed, but is not in the default execution path, it is
340
+ possible to specify its location by setting the environment variable `MAPILLARY_TOOLS_EXIFTOOL_PATH`.
341
+ * Pattern are case-sensitive or not depending on the filesystem - in Windows, `%g.gpx` will match both `basename.gpx`
342
+ and `basename.GPX`, in MacOs, Linux or other Unix systems no.
343
+ * If both `--video_geotag_source` and `--geotag_source` are specified, `--video_geotag_source` will apply to video files
344
+ and `--geotag_source` to image files.
345
+
346
+ ### Examples
347
+
348
+ #### Generic supported videos
349
+
350
+ Process all videos in a directory, trying to parse them as CAMM, GoPro or BlackVue:
351
+ ```sh
352
+ mapillary_tools process --video_geotag_source video VIDEO_DIR/
353
+ ```
354
+
355
+ #### External GPX
356
+
357
+ Process all videos in a directory, taking geolocation data from GPX files. A video named `foo.mp4` will be associated
358
+ with a GPX file called `foo.gpx`.
359
+ ```sh
360
+ mapillary_tools process --video_geotag_source gpx VIDEO_DIR/
361
+ ```
362
+
363
+ #### Insta360 stitched videos
364
+
365
+ The videos to process have been stitched by Insta360 Studio; the geolocation data is in the original
366
+ videos in the parent directory, and there may be GPX files alongside the stitched video.
367
+ First look for GPX, then fallback to running exiftool against the original videos.
368
+ ```sh
369
+ mapillary_tools process \
370
+ --video_geotag_source gpx \
371
+ --video_geotag_source '{"source": "exiftool_runtime", "pattern": "../%g.insv"}' \
372
+ VIDEO_DIR/
373
+ ```
374
+
375
+ ### Limitations of `--video_geotag_source`
376
+
377
+ **External geolocation sources will be aligned with the start of video, there is
378
+ currently no way of applying offsets or scaling the time.** This means, for instance, that GPX tracks must begin precisely
379
+ at the instant of the video, and that timelapse videos are supported only for sources `camm, gopro, blackvue`.
380
+
381
+
306
382
  ## Authenticate
307
383
 
308
384
  The command `authenticate` will update the user credentials stored in the config file.
@@ -0,0 +1 @@
1
+ VERSION = "0.11.0b2"
@@ -1,15 +1,8 @@
1
1
  import os
2
- import sys
3
2
  import typing as T
4
- from typing import Union
5
3
 
6
4
  import requests
7
5
 
8
- if sys.version_info >= (3, 8):
9
- from typing import Literal # pylint: disable=no-name-in-module
10
- else:
11
- from typing_extensions import Literal
12
-
13
6
  MAPILLARY_CLIENT_TOKEN = os.getenv(
14
7
  "MAPILLARY_CLIENT_TOKEN", "MLY|5675152195860640|6b02c72e6e3c801e5603ab0495623282"
15
8
  )
@@ -31,7 +24,7 @@ def get_upload_token(email: str, password: str) -> requests.Response:
31
24
 
32
25
 
33
26
  def fetch_organization(
34
- user_access_token: str, organization_id: Union[int, str]
27
+ user_access_token: str, organization_id: T.Union[int, str]
35
28
  ) -> requests.Response:
36
29
  resp = requests.get(
37
30
  f"{MAPILLARY_GRAPH_API_ENDPOINT}/{organization_id}",
@@ -47,7 +40,7 @@ def fetch_organization(
47
40
  return resp
48
41
 
49
42
 
50
- ActionType = Literal[
43
+ ActionType = T.Literal[
51
44
  "upload_started_upload", "upload_finished_upload", "upload_failed_upload"
52
45
  ]
53
46
 
@@ -63,14 +63,14 @@ def add_general_arguments(parser, command):
63
63
  elif command in ["upload"]:
64
64
  parser.add_argument(
65
65
  "import_path",
66
- help="Path to your images.",
66
+ help="Paths to your images or videos.",
67
67
  nargs="+",
68
68
  type=Path,
69
69
  )
70
70
  elif command in ["process", "process_and_upload"]:
71
71
  parser.add_argument(
72
72
  "import_path",
73
- help="Path to your images.",
73
+ help="Paths to your images or videos.",
74
74
  nargs="+",
75
75
  type=Path,
76
76
  )
@@ -167,7 +167,9 @@ def main():
167
167
  try:
168
168
  args.func(argvars)
169
169
  except exceptions.MapillaryUserError as exc:
170
- LOG.error("%s: %s", exc.__class__.__name__, exc)
170
+ LOG.error(
171
+ "%s: %s", exc.__class__.__name__, exc, exc_info=log_level == logging.DEBUG
172
+ )
171
173
  sys.exit(exc.exit_code)
172
174
 
173
175
 
@@ -171,6 +171,13 @@ class Command:
171
171
  required=False,
172
172
  type=Path,
173
173
  )
174
+ group_geotagging.add_argument(
175
+ "--video_geotag_source",
176
+ help="Name of the video data extractor and optional arguments. Can be specified multiple times. See the documentation for details. [Experimental, subject to change]",
177
+ action="append",
178
+ default=[],
179
+ required=False,
180
+ )
174
181
  group_geotagging.add_argument(
175
182
  "--interpolation_use_gpx_start_time",
176
183
  help=f"If supplied, the first image will use the first GPX point time for interpolation, which means the image location will be interpolated to the first GPX point too. Only works for geotagging from {', '.join(geotag_gpx_based_sources)}.",
@@ -261,13 +268,14 @@ class Command:
261
268
  vars_args["duplicate_angle"] = 360
262
269
 
263
270
  metadatas = process_geotag_properties(
271
+ vars_args=vars_args,
264
272
  **(
265
273
  {
266
274
  k: v
267
275
  for k, v in vars_args.items()
268
276
  if k in inspect.getfullargspec(process_geotag_properties).args
269
277
  }
270
- )
278
+ ),
271
279
  )
272
280
 
273
281
  metadatas = process_import_meta_properties(
@@ -18,6 +18,7 @@ VIDEO_SAMPLE_DISTANCE = float(os.getenv(_ENV_PREFIX + "VIDEO_SAMPLE_DISTANCE", 3
18
18
  VIDEO_DURATION_RATIO = float(os.getenv(_ENV_PREFIX + "VIDEO_DURATION_RATIO", 1))
19
19
  FFPROBE_PATH: str = os.getenv(_ENV_PREFIX + "FFPROBE_PATH", "ffprobe")
20
20
  FFMPEG_PATH: str = os.getenv(_ENV_PREFIX + "FFMPEG_PATH", "ffmpeg")
21
+ EXIFTOOL_PATH: str = os.getenv(_ENV_PREFIX + "EXIFTOOL_PATH", "exiftool")
21
22
  IMAGE_DESCRIPTION_FILENAME = os.getenv(
22
23
  _ENV_PREFIX + "IMAGE_DESCRIPTION_FILENAME", "mapillary_image_description.json"
23
24
  )
@@ -34,6 +34,10 @@ class MapillaryFFmpegNotFoundError(MapillaryUserError):
34
34
  help = "https://github.com/mapillary/mapillary_tools#video-support"
35
35
 
36
36
 
37
+ class MapillaryExiftoolNotFoundError(MapillaryUserError):
38
+ exit_code = 8
39
+
40
+
37
41
  class MapillaryDescriptionError(Exception):
38
42
  pass
39
43
 
@@ -1,8 +1,10 @@
1
1
  import abc
2
2
  import datetime
3
3
  import logging
4
+ import re
4
5
  import typing as T
5
6
  import xml.etree.ElementTree as et
7
+ from fractions import Fraction
6
8
  from pathlib import Path
7
9
 
8
10
  import exifread
@@ -21,6 +23,8 @@ XMP_NAMESPACES = {
21
23
  # https://github.com/ianare/exif-py/issues/167
22
24
  EXIFREAD_LOG = logging.getLogger("exifread")
23
25
  EXIFREAD_LOG.setLevel(logging.ERROR)
26
+ SIGN_BY_DIRECTION = {None: 1, "N": 1, "S": -1, "E": 1, "W": -1}
27
+ ADOBE_FORMAT_REGEX = re.compile(r"(\d+),(\d{1,3}\.?\d*)([NSWE])")
24
28
 
25
29
 
26
30
  def eval_frac(value: Ratio) -> float:
@@ -47,6 +51,38 @@ def gps_to_decimal(values: T.Tuple[Ratio, Ratio, Ratio]) -> T.Optional[float]:
47
51
  return degrees + minutes / 60 + seconds / 3600
48
52
 
49
53
 
54
+ def _parse_coord_numeric(coord: str, ref: T.Optional[str]) -> T.Optional[float]:
55
+ try:
56
+ return float(coord) * SIGN_BY_DIRECTION[ref]
57
+ except (ValueError, KeyError):
58
+ return None
59
+
60
+
61
+ def _parse_coord_adobe(coord: str) -> T.Optional[float]:
62
+ """
63
+ Parse Adobe coordinate format: <degrees,fractionalminutes[NSEW]>
64
+ """
65
+ matches = ADOBE_FORMAT_REGEX.match(coord)
66
+ if matches:
67
+ deg = Ratio(int(matches.group(1)), 1)
68
+ min_frac = Fraction.from_float(float(matches.group(2)))
69
+ min = Ratio(min_frac.numerator, min_frac.denominator)
70
+ sec = Ratio(0, 1)
71
+ converted = gps_to_decimal((deg, min, sec))
72
+ if converted is not None:
73
+ return converted * SIGN_BY_DIRECTION[matches.group(3)]
74
+ return None
75
+
76
+
77
+ def _parse_coord(coord: T.Optional[str], ref: T.Optional[str]) -> T.Optional[float]:
78
+ if coord is None:
79
+ return None
80
+ parsed = _parse_coord_numeric(coord, ref)
81
+ if parsed is None:
82
+ parsed = _parse_coord_adobe(coord)
83
+ return parsed
84
+
85
+
50
86
  def _parse_iso(dtstr: str) -> T.Optional[datetime.datetime]:
51
87
  try:
52
88
  return datetime.datetime.fromisoformat(dtstr)
@@ -378,22 +414,22 @@ class ExifReadFromXMP(ExifReadABC):
378
414
  )
379
415
 
380
416
  def extract_lon_lat(self) -> T.Optional[T.Tuple[float, float]]:
381
- lat = self._extract_alternative_fields(["exif:GPSLatitude"], float)
417
+ lat_ref = self._extract_alternative_fields(["exif:GPSLatitudeRef"], str)
418
+ lat_str: T.Optional[str] = self._extract_alternative_fields(
419
+ ["exif:GPSLatitude"], str
420
+ )
421
+ lat: T.Optional[float] = _parse_coord(lat_str, lat_ref)
382
422
  if lat is None:
383
423
  return None
384
424
 
385
- lon = self._extract_alternative_fields(["exif:GPSLongitude"], float)
425
+ lon_ref = self._extract_alternative_fields(["exif:GPSLongitudeRef"], str)
426
+ lon_str: T.Optional[str] = self._extract_alternative_fields(
427
+ ["exif:GPSLongitude"], str
428
+ )
429
+ lon = _parse_coord(lon_str, lon_ref)
386
430
  if lon is None:
387
431
  return None
388
432
 
389
- ref = self._extract_alternative_fields(["exif:GPSLongitudeRef"], str)
390
- if ref and ref.upper() == "W":
391
- lon = -1 * lon
392
-
393
- ref = self._extract_alternative_fields(["exif:GPSLatitudeRef"], str)
394
- if ref and ref.upper() == "S":
395
- lat = -1 * lat
396
-
397
433
  return lon, lat
398
434
 
399
435
  def extract_make(self) -> T.Optional[str]:
@@ -310,6 +310,10 @@ class ExifToolRead(exif_read.ExifReadABC):
310
310
  if lon_lat is not None:
311
311
  return lon_lat
312
312
 
313
+ lon_lat = self._extract_lon_lat("XMP-exif:GPSLongitude", "XMP-exif:GPSLatitude")
314
+ if lon_lat is not None:
315
+ return lon_lat
316
+
313
317
  return None
314
318
 
315
319
  def _extract_lon_lat(
@@ -11,22 +11,17 @@ import tempfile
11
11
  import typing as T
12
12
  from pathlib import Path
13
13
 
14
- if sys.version_info >= (3, 8):
15
- from typing import TypedDict # pylint: disable=no-name-in-module
16
- else:
17
- from typing_extensions import TypedDict
18
-
19
14
  LOG = logging.getLogger(__name__)
20
15
  FRAME_EXT = ".jpg"
21
16
  NA_STREAM_IDX = "NA"
22
17
 
23
18
 
24
- class StreamTag(TypedDict):
19
+ class StreamTag(T.TypedDict):
25
20
  creation_time: str
26
21
  language: str
27
22
 
28
23
 
29
- class Stream(TypedDict):
24
+ class Stream(T.TypedDict):
30
25
  codec_name: str
31
26
  codec_tag_string: str
32
27
  codec_type: str
@@ -37,7 +32,7 @@ class Stream(TypedDict):
37
32
  width: int
38
33
 
39
34
 
40
- class ProbeOutput(TypedDict):
35
+ class ProbeOutput(T.TypedDict):
41
36
  streams: T.List[Stream]
42
37
 
43
38
 
@@ -1,17 +1,11 @@
1
1
  # pyre-ignore-all-errors[5, 16, 21, 58]
2
2
 
3
- import sys
4
3
  import typing as T
5
4
 
6
- if sys.version_info >= (3, 8):
7
- from typing import Literal, TypedDict # pylint: disable=no-name-in-module
8
- else:
9
- from typing_extensions import Literal, TypedDict
10
-
11
5
  import construct as C
12
6
 
13
7
 
14
- BoxType = Literal[
8
+ BoxType = T.Literal[
15
9
  b"@mak",
16
10
  b"@mod",
17
11
  b"co64",
@@ -46,7 +40,7 @@ BoxType = Literal[
46
40
  ]
47
41
 
48
42
 
49
- class BoxDict(TypedDict, total=True):
43
+ class BoxDict(T.TypedDict, total=True):
50
44
  type: BoxType
51
45
  data: T.Union[T.Sequence["BoxDict"], T.Dict[str, T.Any], bytes]
52
46
 
@@ -1,13 +1,7 @@
1
1
  import io
2
2
  import pathlib
3
- import sys
4
3
  import typing as T
5
4
 
6
- if sys.version_info >= (3, 8):
7
- from typing import TypedDict # pylint: disable=no-name-in-module
8
- else:
9
- from typing_extensions import TypedDict
10
-
11
5
  import construct as C
12
6
 
13
7
  from .. import geo
@@ -35,7 +29,7 @@ NOTE:
35
29
  """
36
30
 
37
31
 
38
- class KLVDict(TypedDict):
32
+ class KLVDict(T.TypedDict):
39
33
  key: bytes
40
34
  type: bytes
41
35
  structure_size: int