mapillary-tools 0.11.1__tar.gz → 0.12.0__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.11.1 → mapillary_tools-0.12.0}/PKG-INFO +4 -4
  2. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/README.md +1 -1
  3. mapillary_tools-0.12.0/mapillary_tools/__init__.py +1 -0
  4. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/api_v4.py +7 -0
  5. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/exif_read.py +3 -3
  6. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/exif_write.py +8 -0
  7. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/exiftool_read.py +2 -2
  8. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/ffmpeg.py +21 -3
  9. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/geotag/gpmf_parser.py +128 -75
  10. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/process_sequence_properties.py +5 -3
  11. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/sample_video.py +17 -15
  12. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/types.py +5 -10
  13. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/upload_api_v4.py +7 -0
  14. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/video_data_extraction/extract_video_data.py +6 -4
  15. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools.egg-info/PKG-INFO +5 -5
  16. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools.egg-info/requires.txt +2 -2
  17. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/requirements.txt +2 -2
  18. mapillary_tools-0.11.1/mapillary_tools/__init__.py +0 -1
  19. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/LICENSE +0 -0
  20. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/MANIFEST.in +0 -0
  21. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/authenticate.py +0 -0
  22. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/commands/__init__.py +0 -0
  23. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/commands/__main__.py +0 -0
  24. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/commands/authenticate.py +0 -0
  25. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/commands/process.py +0 -0
  26. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/commands/process_and_upload.py +0 -0
  27. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/commands/sample_video.py +0 -0
  28. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/commands/upload.py +0 -0
  29. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/commands/upload_blackvue.py +0 -0
  30. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/commands/upload_camm.py +0 -0
  31. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/commands/upload_zip.py +0 -0
  32. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/commands/video_process.py +0 -0
  33. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/commands/video_process_and_upload.py +0 -0
  34. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/commands/zip.py +0 -0
  35. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/config.py +0 -0
  36. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/constants.py +0 -0
  37. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/exceptions.py +0 -0
  38. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/exiftool_read_video.py +0 -0
  39. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/geo.py +0 -0
  40. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/geotag/__init__.py +0 -0
  41. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/geotag/blackvue_parser.py +0 -0
  42. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/geotag/camm_builder.py +0 -0
  43. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/geotag/camm_parser.py +0 -0
  44. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/geotag/construct_mp4_parser.py +0 -0
  45. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/geotag/geotag_from_generic.py +0 -0
  46. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/geotag/geotag_images_from_exif.py +0 -0
  47. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/geotag/geotag_images_from_exiftool.py +0 -0
  48. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/geotag/geotag_images_from_exiftool_both_image_and_video.py +0 -0
  49. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/geotag/geotag_images_from_gpx.py +0 -0
  50. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/geotag/geotag_images_from_gpx_file.py +0 -0
  51. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/geotag/geotag_images_from_nmea_file.py +0 -0
  52. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/geotag/geotag_images_from_video.py +0 -0
  53. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/geotag/geotag_videos_from_exiftool_video.py +0 -0
  54. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/geotag/geotag_videos_from_video.py +0 -0
  55. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/geotag/gpmf_gps_filter.py +0 -0
  56. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/geotag/gps_filter.py +0 -0
  57. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/geotag/io_utils.py +0 -0
  58. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/geotag/mp4_sample_parser.py +0 -0
  59. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/geotag/simple_mp4_builder.py +0 -0
  60. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/geotag/simple_mp4_parser.py +0 -0
  61. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/geotag/utils.py +0 -0
  62. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/history.py +0 -0
  63. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/ipc.py +0 -0
  64. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/process_geotag_properties.py +0 -0
  65. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/process_import_meta_properties.py +0 -0
  66. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/upload.py +0 -0
  67. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/uploader.py +0 -0
  68. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/utils.py +0 -0
  69. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/video_data_extraction/cli_options.py +0 -0
  70. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/video_data_extraction/extractors/base_parser.py +0 -0
  71. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/video_data_extraction/extractors/blackvue_parser.py +0 -0
  72. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/video_data_extraction/extractors/camm_parser.py +0 -0
  73. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/video_data_extraction/extractors/exiftool_runtime_parser.py +0 -0
  74. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/video_data_extraction/extractors/exiftool_xml_parser.py +0 -0
  75. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/video_data_extraction/extractors/generic_video_parser.py +0 -0
  76. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/video_data_extraction/extractors/gopro_parser.py +0 -0
  77. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/video_data_extraction/extractors/gpx_parser.py +0 -0
  78. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/video_data_extraction/extractors/nmea_parser.py +0 -0
  79. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools/video_data_extraction/video_data_parser_factory.py +0 -0
  80. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools.egg-info/SOURCES.txt +0 -0
  81. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools.egg-info/dependency_links.txt +0 -0
  82. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools.egg-info/entry_points.txt +0 -0
  83. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/mapillary_tools.egg-info/top_level.txt +0 -0
  84. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/schema/image_description_schema.json +0 -0
  85. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/setup.cfg +0 -0
  86. {mapillary_tools-0.11.1 → mapillary_tools-0.12.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mapillary_tools
3
- Version: 0.11.1
3
+ Version: 0.12.0
4
4
  Summary: Mapillary Image/Video Import Pipeline
5
5
  Home-page: https://github.com/mapillary/mapillary_tools
6
6
  Author: Mapillary
@@ -13,8 +13,8 @@ Requires-Dist: construct<3.0.0,>=2.10.0
13
13
  Requires-Dist: exifread==2.3.2
14
14
  Requires-Dist: piexif==1.1.3
15
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
16
+ Requires-Dist: pynmea2<2.0.0,>=1.12.0
17
+ Requires-Dist: requests[socks]<3.0.0,>=2.20.0
18
18
  Requires-Dist: tqdm<5.0,>=4.0
19
19
  Requires-Dist: typing_extensions
20
20
  Requires-Dist: jsonschema~=4.17.3
@@ -130,7 +130,7 @@ A command line program such as Termux is required. Installation can be done with
130
130
  commands will install Python 3, pip3, git, and all required libraries for mapillary_tools on Termux:
131
131
 
132
132
  ```sh
133
- pkg install python git build-essential libgeos openssl libjpeg-turbo
133
+ pkg install python git build-essential libgeos openssl libjpeg-turbo libexpat libexpat-static
134
134
  pip install --upgrade pip wheel
135
135
  pip install --upgrade mapillary_tools
136
136
  ```
@@ -108,7 +108,7 @@ A command line program such as Termux is required. Installation can be done with
108
108
  commands will install Python 3, pip3, git, and all required libraries for mapillary_tools on Termux:
109
109
 
110
110
  ```sh
111
- pkg install python git build-essential libgeos openssl libjpeg-turbo
111
+ pkg install python git build-essential libgeos openssl libjpeg-turbo libexpat libexpat-static
112
112
  pip install --upgrade pip wheel
113
113
  pip install --upgrade mapillary_tools
114
114
  ```
@@ -0,0 +1 @@
1
+ VERSION = "0.12.0"
@@ -9,6 +9,10 @@ MAPILLARY_CLIENT_TOKEN = os.getenv(
9
9
  MAPILLARY_GRAPH_API_ENDPOINT = os.getenv(
10
10
  "MAPILLARY_GRAPH_API_ENDPOINT", "https://graph.mapillary.com"
11
11
  )
12
+ # https://requests.readthedocs.io/en/latest/user/advanced/#ssl-cert-verification
13
+ MAPILLARY__DISABLE_VERIFYING_SSL = (
14
+ os.getenv("MAPILLARY__DISABLE_VERIFYING_SSL") == "TRUE"
15
+ )
12
16
  REQUESTS_TIMEOUT = 60 # 1 minutes
13
17
 
14
18
 
@@ -18,6 +22,7 @@ def get_upload_token(email: str, password: str) -> requests.Response:
18
22
  params={"access_token": MAPILLARY_CLIENT_TOKEN},
19
23
  json={"email": email, "password": password, "locale": "en_US"},
20
24
  timeout=REQUESTS_TIMEOUT,
25
+ verify=not MAPILLARY__DISABLE_VERIFYING_SSL,
21
26
  )
22
27
  resp.raise_for_status()
23
28
  return resp
@@ -35,6 +40,7 @@ def fetch_organization(
35
40
  "Authorization": f"OAuth {user_access_token}",
36
41
  },
37
42
  timeout=REQUESTS_TIMEOUT,
43
+ verify=not MAPILLARY__DISABLE_VERIFYING_SSL,
38
44
  )
39
45
  resp.raise_for_status()
40
46
  return resp
@@ -56,6 +62,7 @@ def logging(action_type: ActionType, properties: T.Dict) -> requests.Response:
56
62
  "Authorization": f"OAuth {MAPILLARY_CLIENT_TOKEN}",
57
63
  },
58
64
  timeout=REQUESTS_TIMEOUT,
65
+ verify=not MAPILLARY__DISABLE_VERIFYING_SSL,
59
66
  )
60
67
  resp.raise_for_status()
61
68
  return resp
@@ -399,7 +399,7 @@ class ExifReadFromXMP(ExifReadABC):
399
399
 
400
400
  def extract_capture_time(self) -> T.Optional[datetime.datetime]:
401
401
  dt = self.extract_gps_datetime()
402
- if dt is not None:
402
+ if dt is not None and dt.date() != datetime.date(1970, 1, 1):
403
403
  return dt
404
404
 
405
405
  dt = self.extract_exif_datetime()
@@ -554,7 +554,7 @@ class ExifReadFromEXIF(ExifReadABC):
554
554
  return None
555
555
 
556
556
  dt = strptime_alternative_formats(gpsdate, ["%Y:%m:%d", "%Y-%m-%d"])
557
- if dt is None:
557
+ if dt is None or dt == datetime.date(1970, 1, 1):
558
558
  return None
559
559
 
560
560
  gpstimestamp = self.tags.get("GPS GPSTimeStamp")
@@ -632,7 +632,7 @@ class ExifReadFromEXIF(ExifReadABC):
632
632
  gps_dt = self.extract_gps_datetime()
633
633
  except (ValueError, TypeError, ZeroDivisionError):
634
634
  gps_dt = None
635
- if gps_dt is not None:
635
+ if gps_dt is not None and gps_dt.date() != datetime.date(1970, 1, 1):
636
636
  return gps_dt
637
637
 
638
638
  dt = self.extract_exif_datetime()
@@ -182,6 +182,14 @@ class ExifEdit:
182
182
  # retry later
183
183
  else:
184
184
  raise exc
185
+ except Exception as exc:
186
+ zeroth_ifd = self._ef.get("0th", {})
187
+ # workaround: https://github.com/mapillary/mapillary_tools/issues/662
188
+ if piexif.ImageIFD.AsShotNeutral in zeroth_ifd:
189
+ del zeroth_ifd[piexif.ImageIFD.AsShotNeutral]
190
+ assert piexif.ImageIFD.AsShotNeutral not in zeroth_ifd
191
+ else:
192
+ raise exc
185
193
  else:
186
194
  break
187
195
 
@@ -266,14 +266,14 @@ class ExifToolRead(exif_read.ExifReadABC):
266
266
  dt = self.extract_gps_datetime()
267
267
  except (ValueError, TypeError, ZeroDivisionError):
268
268
  dt = None
269
- if dt is not None:
269
+ if dt is not None and dt.date() != datetime.date(1970, 1, 1):
270
270
  return dt
271
271
 
272
272
  try:
273
273
  dt = self.extract_gps_datetime_from_xmp()
274
274
  except (ValueError, TypeError, ZeroDivisionError):
275
275
  dt = None
276
- if dt is not None:
276
+ if dt is not None and dt.date() != datetime.date(1970, 1, 1):
277
277
  return dt
278
278
 
279
279
  dt = self.extract_exif_datetime()
@@ -195,6 +195,18 @@ class FFMPEG:
195
195
 
196
196
  self._run_ffmpeg(cmd)
197
197
 
198
+ def generate_binary_search(self, sorted_frame_indices: T.Sequence[int]) -> str:
199
+ length = len(sorted_frame_indices)
200
+
201
+ if length == 0:
202
+ return "0"
203
+
204
+ if length == 1:
205
+ return f"eq(n\\,{ sorted_frame_indices[0] })"
206
+
207
+ middle = length // 2
208
+ return f"if(lt(n\\,{ sorted_frame_indices[middle] })\\,{ self.generate_binary_search(sorted_frame_indices[:middle]) }\\,{ self.generate_binary_search(sorted_frame_indices[middle:]) })"
209
+
198
210
  def extract_specified_frames(
199
211
  self,
200
212
  video_path: Path,
@@ -226,7 +238,7 @@ class FFMPEG:
226
238
  # the maximum command line length for the CreateProcess function is 32767 characters
227
239
  # https://devblogs.microsoft.com/oldnewthing/20031210-00/?p=41553
228
240
 
229
- eqs = "+".join(f"eq(n\\,{idx})" for idx in sorted(frame_indices))
241
+ eqs = self.generate_binary_search(sorted(frame_indices))
230
242
 
231
243
  # https://github.com/mapillary/mapillary_tools/issues/503
232
244
  if sys.platform in ["win32"]:
@@ -252,12 +264,18 @@ class FFMPEG:
252
264
  *[
253
265
  *["-filter_script:v", select_file.name],
254
266
  # Each frame is passed with its timestamp from the demuxer to the muxer
255
- # vsync is deprecated but -fps_mode is not avaliable on some versions ;(
256
267
  *["-vsync", "0"],
268
+ # vsync is deprecated by fps_mode,
269
+ # but fps_mode is not avaliable on some older versions ;(
257
270
  # *[f"-fps_mode:{stream_specifier}", "passthrough"],
258
271
  # Set the number of video frames to output
259
272
  *[f"-frames:{stream_specifier}", str(len(frame_indices))],
260
- *["-frame_pts", "1"],
273
+ # Disabled because it doesn't always name the sample images as expected
274
+ # For example "select(n\,1)" we expected the first sample to be IMG_001.JPG
275
+ # but it could be IMG_005.JPG
276
+ # https://www.ffmpeg.org/ffmpeg-formats.html#Options-21
277
+ # If set to 1, expand the filename with pts from pkt->pts. Default value is 0.
278
+ # *["-frame_pts", "1"],
261
279
  ],
262
280
  # video quality level (or the alias -q:v)
263
281
  *[f"-qscale:{stream_specifier}", "2"],
@@ -15,15 +15,16 @@ A GPS GPMF sample has the following structure:
15
15
  - DVID: Auto generated unique-ID for managing a large number of connect devices
16
16
  - STRM: Metadata streams are each nested with STRM
17
17
  - GPS5: latitude, longitude, altitude (WGS 84), 2D ground speed, and 3D speed
18
+ - GPS9: lat, long, alt, 2D speed, 3D speed, days since 2000, secs since midnight (ms precision), DOP, fix (0, 2D or 3D)
18
19
  - GPSA: not documented in the spec
19
20
  - GPSF: Within the GPS stream: 0 - no lock, 2 or 3 - 2D or 3D Lock.
20
21
  - GPSP: GPS Precision - Dilution of Precision (DOP x100). Under 500 is good.
21
22
  - GPSU: UTC time and data from GPS. The time is read from from another clock so it is not in use.
22
- - SCAL: Scaling factor (divisor) for GPS5
23
+ - SCAL: Scaling factor (divisor) for GPS5 and GPS9
23
24
 
24
25
  NOTE:
25
26
  - There might be multiple DEVC streams.
26
- - Only GPS5 and SCAL are required. The others are optional.
27
+ - Only GPS5, GPS9, and SCAL are required. The others are optional.
27
28
  - GPSU is not in use. We use the video clock to make sure frames and GPS are in sync.
28
29
  - We should skip samples with GPSF==0 or GPSP > 500
29
30
  """
@@ -40,6 +41,66 @@ class KLVDict(T.TypedDict):
40
41
  GPMFSampleData: C.GreedyRange
41
42
 
42
43
 
44
+ # type char: (construct type, size in bytes)
45
+ _type_mapping = {
46
+ # b single byte signed integer int8_t -128 to 127
47
+ b"b": (C.Int8sb, 1),
48
+ # B single byte unsigned integer uint8_t 0 to 255
49
+ b"B": (C.Int8ub, 1),
50
+ # c single byte 'c' style ASCII character string char Optionally NULL terminated - size/repeat sets the length
51
+ b"c": (C.Bytes(1), 1),
52
+ # d 64-bit double precision (IEEE 754) double
53
+ b"d": (C.Float64b, 8),
54
+ # f 32-bit float (IEEE 754) float
55
+ b"f": (C.Float32b, 4),
56
+ # F 32-bit four character key -- FourCC char fourcc[4]
57
+ b"F": (C.Bytes(4), 4),
58
+ # G 128-bit ID (like UUID) uint8_t guid[16]
59
+ b"G": (C.Bytes(16), 16),
60
+ # j 64-bit signed long number int64_t
61
+ b"j": (C.Int64sb, 8),
62
+ # J 64-bit unsigned long number uint64_t
63
+ b"J": (C.Int64ub, 8),
64
+ # l 32-bit signed integer int32_t
65
+ b"l": (C.Int32sb, 4),
66
+ # L 32-bit unsigned integer uint32_t
67
+ b"L": (C.Int32ub, 4),
68
+ # q 32-bit Q Number Q15.16 uint32_t 16-bit integer (A) with 16-bit fixed point (B) for A.B value (range -32768.0 to 32767.99998)
69
+ b"q": (C.Int32ub, 4),
70
+ # Q 64-bit Q Number Q31.32 uint64_t 32-bit integer (A) with 32-bit fixed point (B) for A.B value.
71
+ b"Q": (C.Int64ub, 8),
72
+ # s 16-bit signed integer int16_t -32768 to 32768
73
+ b"s": (C.Int16sb, 2),
74
+ # S 16-bit unsigned integer uint16_t 0 to 65536
75
+ b"S": (C.Int16ub, 2),
76
+ # U UTC Date and Time string char utcdate[16] Date + UTC Time format yymmddhhmmss.sss - (years 20xx covered)
77
+ b"U": (C.Bytes(16), 16),
78
+ }
79
+
80
+
81
+ _klv_data_switch = C.Switch(
82
+ C.this.type,
83
+ {
84
+ **{
85
+ type_char: C.Array(
86
+ C.this.repeat, C.Array(C.this.structure_size // size, ctype)
87
+ )
88
+ for type_char, (ctype, size) in _type_mapping.items()
89
+ },
90
+ # c single byte 'c' style ASCII character string char Optionally NULL terminated - size/repeat sets the length
91
+ b"c": C.Array(
92
+ C.this.repeat, C.Bytes(C.this.structure_size)
93
+ ), # overwrite the one in _type_mapping to make sure it returns bytes instead of a list of bytes
94
+ # null Nested metadata uint32_t The data within is GPMF structured KLV data
95
+ b"\x00": C.FixedSized(
96
+ (C.this.repeat * C.this.structure_size),
97
+ C.LazyBound(lambda: GPMFSampleData),
98
+ ),
99
+ },
100
+ C.Array(C.this.repeat, C.Bytes(C.this.structure_size)),
101
+ )
102
+
103
+
43
104
  KLV = C.Struct(
44
105
  # FourCC
45
106
  "key" / C.Bytes(4),
@@ -52,77 +113,7 @@ KLV = C.Struct(
52
113
  # this is the Repeat field. Struct Size and the Repeat allow for up to
53
114
  # 16.7MB of data in a single KLV GPMF payload.
54
115
  "repeat" / C.Int16ub,
55
- "data"
56
- / C.Switch(
57
- C.this.type,
58
- {
59
- # b single byte signed integer int8_t -128 to 127
60
- b"b": C.Array(C.this.repeat, C.Array(C.this.structure_size, C.Int8sb)),
61
- # B single byte unsigned integer uint8_t 0 to 255
62
- b"B": C.Array(C.this.repeat, C.Array(C.this.structure_size, C.Int8ub)),
63
- # c single byte 'c' style ASCII character string char Optionally NULL terminated - size/repeat sets the length
64
- b"c": C.Array(C.this.repeat, C.Bytes(C.this.structure_size)),
65
- # d 64-bit double precision (IEEE 754) double
66
- b"d": C.Array(
67
- C.this.repeat, C.Array(C.this.structure_size // 8, C.Float64b)
68
- ),
69
- # f 32-bit float (IEEE 754) float
70
- b"f": C.Array(
71
- C.this.repeat, C.Array(C.this.structure_size // 4, C.Float32b)
72
- ),
73
- # F 32-bit four character key -- FourCC char fourcc[4]
74
- b"F": C.Array(
75
- C.this.repeat, C.Array(C.this.structure_size // 4, C.Bytes(4))
76
- ),
77
- # G 128-bit ID (like UUID) uint8_t guid[16]
78
- b"G": C.Array(
79
- C.this.repeat, C.Array(C.this.structure_size // 16, C.Bytes(16))
80
- ),
81
- # j 64-bit signed unsigned number int64_t
82
- b"j": C.Array(
83
- C.this.repeat, C.Array(C.this.structure_size // 8, C.Int64sb)
84
- ),
85
- # J 64-bit unsigned unsigned number uint64_t
86
- b"J": C.Array(
87
- C.this.repeat, C.Array(C.this.structure_size // 8, C.Int64ub)
88
- ),
89
- # l 32-bit signed integer int32_t
90
- b"l": C.Array(
91
- C.this.repeat, C.Array(C.this.structure_size // 4, C.Int32sb)
92
- ),
93
- # L 32-bit unsigned integer uint32_t
94
- b"L": C.Array(
95
- C.this.repeat, C.Array(C.this.structure_size // 4, C.Int32ub)
96
- ),
97
- # q 32-bit Q Number Q15.16 uint32_t 16-bit integer (A) with 16-bit fixed point (B) for A.B value (range -32768.0 to 32767.99998)
98
- b"q": C.Array(
99
- C.this.repeat, C.Array(C.this.structure_size // 4, C.Int32ub)
100
- ),
101
- # Q 64-bit Q Number Q31.32 uint64_t 32-bit integer (A) with 32-bit fixed point (B) for A.B value.
102
- b"Q": C.Array(
103
- C.this.repeat, C.Array(C.this.structure_size // 8, C.Int64ub)
104
- ),
105
- # s 16-bit signed integer int16_t -32768 to 32768
106
- b"s": C.Array(
107
- C.this.repeat, C.Array(C.this.structure_size // 2, C.Int16sb)
108
- ),
109
- # S 16-bit unsigned integer uint16_t 0 to 65536
110
- b"S": C.Array(
111
- C.this.repeat, C.Array(C.this.structure_size // 2, C.Int16ub)
112
- ),
113
- # U UTC Date and Time string char utcdate[16] Date + UTC Time format yymmddhhmmss.sss - (years 20xx covered)
114
- b"U": C.Array(
115
- C.this.repeat, C.Array(C.this.structure_size // 16, C.Bytes(16))
116
- ),
117
- # ? data structure is complex TYPE Structure is defined with a preceding TYPE
118
- # null Nested metadata uint32_t The data within is GPMF structured KLV data
119
- b"\x00": C.FixedSized(
120
- (C.this.repeat * C.this.structure_size),
121
- C.LazyBound(lambda: GPMFSampleData),
122
- ),
123
- },
124
- C.Array(C.this.repeat, C.Bytes(C.this.structure_size)),
125
- ),
116
+ "data" / _klv_data_switch,
126
117
  C.IfThenElse(
127
118
  (C.this.repeat * C.this.structure_size) % 4 == 0,
128
119
  C.Padding(0),
@@ -170,7 +161,7 @@ GPMFSampleData = C.GreedyRange(KLV)
170
161
  # [378081666, -1224280064, 9621, 1492, 138],
171
162
  # [378081662, -1224280049, 9592, 1476, 150],
172
163
  # ]
173
- def gps_from_stream(
164
+ def gps5_from_stream(
174
165
  stream: T.Sequence[KLVDict],
175
166
  ) -> T.Generator[geo.PointWithFix, None, None]:
176
167
  indexed: T.Dict[bytes, T.List[T.List[T.Any]]] = {
@@ -217,6 +208,64 @@ def gps_from_stream(
217
208
  )
218
209
 
219
210
 
211
+ def gps9_from_stream(
212
+ stream: T.Sequence[KLVDict],
213
+ ) -> T.Generator[geo.PointWithFix, None, None]:
214
+ indexed: T.Dict[bytes, T.List[T.List[T.Any]]] = {
215
+ klv["key"]: klv["data"] for klv in stream
216
+ }
217
+
218
+ gps9 = indexed.get(b"GPS9")
219
+ if gps9 is None:
220
+ return
221
+
222
+ scal = indexed.get(b"SCAL")
223
+ if scal is None:
224
+ return
225
+ scal_values = [s[0] for s in scal]
226
+ if any(s == 0 for s in scal_values):
227
+ return
228
+
229
+ type = indexed.get(b"TYPE")
230
+ if type is None:
231
+ return
232
+ gps_value_types = type[0]
233
+
234
+ try:
235
+ sample_parser = C.Sequence(
236
+ *[_type_mapping[t.to_bytes()][0] for t in gps_value_types]
237
+ )
238
+ except Exception as ex:
239
+ raise ValueError(f"Error parsing the complex type {gps_value_types}: {ex}")
240
+
241
+ for sample_data_bytes in gps9:
242
+ sample_data = sample_parser.parse(sample_data_bytes)
243
+
244
+ (
245
+ lat,
246
+ lon,
247
+ alt,
248
+ speed_2d,
249
+ _speed_3d,
250
+ _days_since_2000,
251
+ _secs_since_midnight,
252
+ dop,
253
+ gps_fix,
254
+ ) = [v / s for v, s in zip(sample_data, scal_values)]
255
+
256
+ yield geo.PointWithFix(
257
+ # will figure out the actual timestamp later
258
+ time=0,
259
+ lat=lat,
260
+ lon=lon,
261
+ alt=alt,
262
+ gps_fix=geo.GPSFix(gps_fix),
263
+ gps_precision=dop * 100,
264
+ gps_ground_speed=speed_2d,
265
+ angle=None,
266
+ )
267
+
268
+
220
269
  def _find_first_device_id(stream: T.Sequence[KLVDict]) -> int:
221
270
  device_id = None
222
271
 
@@ -238,7 +287,11 @@ def _find_first_gps_stream(stream: T.Sequence[KLVDict]) -> T.List[geo.PointWithF
238
287
 
239
288
  for klv in stream:
240
289
  if klv["key"] == b"STRM":
241
- sample_points = list(gps_from_stream(klv["data"]))
290
+ sample_points = list(gps9_from_stream(klv["data"]))
291
+ if sample_points:
292
+ break
293
+
294
+ sample_points = list(gps5_from_stream(klv["data"]))
242
295
  if sample_points:
243
296
  break
244
297
 
@@ -195,9 +195,11 @@ def _interpolate_subsecs_for_sorting(sequence: PointSequence) -> None:
195
195
 
196
196
  t = sequence[gidx].time
197
197
  nt = min(
198
- sequence[gidx + len(group)].time
199
- if gidx + len(group) < len(sequence)
200
- else math.floor(t + 1.0),
198
+ (
199
+ sequence[gidx + len(group)].time
200
+ if gidx + len(group) < len(sequence)
201
+ else math.floor(t + 1.0)
202
+ ),
201
203
  math.floor(t + 1.0),
202
204
  )
203
205
  assert t <= nt, f"expect sorted but got {t} > {nt}"
@@ -320,35 +320,37 @@ def _sample_single_video_by_distance(
320
320
  sample_points_by_frame_idx = _sample_video_stream_by_distance(
321
321
  video_metadata.points, video_track_parser, sample_distance
322
322
  )
323
+ sorted_sample_indices = sorted(sample_points_by_frame_idx.keys())
323
324
 
324
325
  with wip_dir_context(wip_sample_dir(sample_dir), sample_dir) as wip_dir:
325
326
  ffmpeg.extract_specified_frames(
326
327
  video_path,
327
328
  wip_dir,
328
- frame_indices=set(sample_points_by_frame_idx.keys()),
329
+ frame_indices=set(sorted_sample_indices),
329
330
  stream_idx=video_stream_idx,
330
331
  )
331
332
 
332
333
  frame_samples = ffmpeglib.sort_selected_samples(
333
334
  wip_dir, video_path, [video_stream_idx]
334
335
  )
335
- # extract_specified_frames() produces 0-based frame indices
336
- for frame_idx_0based, sample_paths in frame_samples:
337
- assert len(sample_paths) == 1
338
- if sample_paths[0] is None:
339
- continue
340
-
341
- sample_point = sample_points_by_frame_idx.get(frame_idx_0based)
342
- if sample_point is None:
343
- # this should not happen
344
- LOG.warning(
345
- "The specified frame index %d was not extracted from stream index %d",
346
- frame_idx_0based,
347
- video_stream_idx,
336
+ if len(frame_samples) != len(sorted_sample_indices):
337
+ raise exceptions.MapillaryVideoError(
338
+ f"Expect {len(sorted_sample_indices)} samples but extracted {len(frame_samples)} samples"
339
+ )
340
+ for idx, (frame_idx_1based, sample_paths) in enumerate(frame_samples):
341
+ assert (
342
+ len(sample_paths) == 1
343
+ ), "Expect 1 sample path at {frame_idx_1based} but got {sample_paths}"
344
+ if idx + 1 != frame_idx_1based:
345
+ raise exceptions.MapillaryVideoError(
346
+ f"Expect {sample_paths[0]} to be {idx + 1}th sample but got {frame_idx_1based}"
348
347
  )
348
+
349
+ for (_, sample_paths), sample_idx in zip(frame_samples, sorted_sample_indices):
350
+ if sample_paths[0] is None:
349
351
  continue
350
352
 
351
- video_sample, interp = sample_point
353
+ video_sample, interp = sample_points_by_frame_idx[sample_idx]
352
354
  assert (
353
355
  interp.time == video_sample.composition_time_offset
354
356
  ), f"interpolated time {interp.time} should match the video sample time {video_sample.composition_time_offset}"
@@ -456,18 +456,15 @@ def map_capture_time_to_datetime(time: str) -> datetime.datetime:
456
456
 
457
457
 
458
458
  @T.overload
459
- def as_desc(metadata: ImageMetadata) -> ImageDescription:
460
- ...
459
+ def as_desc(metadata: ImageMetadata) -> ImageDescription: ...
461
460
 
462
461
 
463
462
  @T.overload
464
- def as_desc(metadata: ErrorMetadata) -> ImageDescriptionError:
465
- ...
463
+ def as_desc(metadata: ErrorMetadata) -> ImageDescriptionError: ...
466
464
 
467
465
 
468
466
  @T.overload
469
- def as_desc(metadata: VideoMetadata) -> VideoDescription:
470
- ...
467
+ def as_desc(metadata: VideoMetadata) -> VideoDescription: ...
471
468
 
472
469
 
473
470
  def as_desc(metadata):
@@ -524,13 +521,11 @@ def _as_image_desc(metadata: ImageMetadata) -> ImageDescription:
524
521
 
525
522
 
526
523
  @T.overload
527
- def from_desc(metadata: ImageDescription) -> ImageMetadata:
528
- ...
524
+ def from_desc(metadata: ImageDescription) -> ImageMetadata: ...
529
525
 
530
526
 
531
527
  @T.overload
532
- def from_desc(metadata: VideoDescription) -> VideoMetadata:
533
- ...
528
+ def from_desc(metadata: VideoDescription) -> VideoMetadata: ...
534
529
 
535
530
 
536
531
  def from_desc(desc):
@@ -15,6 +15,10 @@ MAPILLARY_UPLOAD_ENDPOINT = os.getenv(
15
15
  MAPILLARY_GRAPH_API_ENDPOINT = os.getenv(
16
16
  "MAPILLARY_GRAPH_API_ENDPOINT", "https://graph.mapillary.com"
17
17
  )
18
+ # https://requests.readthedocs.io/en/latest/user/advanced/#ssl-cert-verification
19
+ MAPILLARY__DISABLE_VERIFYING_SSL = (
20
+ os.getenv("MAPILLARY__DISABLE_VERIFYING_SSL") == "TRUE"
21
+ )
18
22
  DEFAULT_CHUNK_SIZE = 1024 * 1024 * 16 # 16MB
19
23
  # According to the docs, UPLOAD_REQUESTS_TIMEOUT can be a tuple of
20
24
  # (connection_timeout, read_timeout): https://requests.readthedocs.io/en/latest/user/advanced/#timeouts
@@ -97,6 +101,7 @@ class UploadService:
97
101
  url,
98
102
  headers=headers,
99
103
  timeout=REQUESTS_TIMEOUT,
104
+ verify=not MAPILLARY__DISABLE_VERIFYING_SSL,
100
105
  )
101
106
  LOG.debug("HTTP response %s: %s", resp.status_code, resp.content)
102
107
  resp.raise_for_status()
@@ -139,6 +144,7 @@ class UploadService:
139
144
  headers=headers,
140
145
  data=chunk,
141
146
  timeout=UPLOAD_REQUESTS_TIMEOUT,
147
+ verify=not MAPILLARY__DISABLE_VERIFYING_SSL,
142
148
  )
143
149
  LOG.debug(
144
150
  "HTTP response %s: %s", resp.status_code, _truncate_end(resp.content)
@@ -185,6 +191,7 @@ class UploadService:
185
191
  headers=headers,
186
192
  json=data,
187
193
  timeout=REQUESTS_TIMEOUT,
194
+ verify=not MAPILLARY__DISABLE_VERIFYING_SSL,
188
195
  )
189
196
  LOG.debug("HTTP response %s: %s", resp.status_code, _truncate_end(resp.content))
190
197
 
@@ -104,10 +104,12 @@ class VideoDataExtractor:
104
104
  else:
105
105
  return ErrorMetadata(
106
106
  filename=file,
107
- error=ex
108
- if ex
109
- else exceptions.MapillaryVideoGPSNotFoundError(
110
- "No GPS data found from the video"
107
+ error=(
108
+ ex
109
+ if ex
110
+ else exceptions.MapillaryVideoGPSNotFoundError(
111
+ "No GPS data found from the video"
112
+ )
111
113
  ),
112
114
  filetype=FileType.VIDEO,
113
115
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
- Name: mapillary-tools
3
- Version: 0.11.1
2
+ Name: mapillary_tools
3
+ Version: 0.12.0
4
4
  Summary: Mapillary Image/Video Import Pipeline
5
5
  Home-page: https://github.com/mapillary/mapillary_tools
6
6
  Author: Mapillary
@@ -13,8 +13,8 @@ Requires-Dist: construct<3.0.0,>=2.10.0
13
13
  Requires-Dist: exifread==2.3.2
14
14
  Requires-Dist: piexif==1.1.3
15
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
16
+ Requires-Dist: pynmea2<2.0.0,>=1.12.0
17
+ Requires-Dist: requests[socks]<3.0.0,>=2.20.0
18
18
  Requires-Dist: tqdm<5.0,>=4.0
19
19
  Requires-Dist: typing_extensions
20
20
  Requires-Dist: jsonschema~=4.17.3
@@ -130,7 +130,7 @@ A command line program such as Termux is required. Installation can be done with
130
130
  commands will install Python 3, pip3, git, and all required libraries for mapillary_tools on Termux:
131
131
 
132
132
  ```sh
133
- pkg install python git build-essential libgeos openssl libjpeg-turbo
133
+ pkg install python git build-essential libgeos openssl libjpeg-turbo libexpat libexpat-static
134
134
  pip install --upgrade pip wheel
135
135
  pip install --upgrade mapillary_tools
136
136
  ```
@@ -3,8 +3,8 @@ construct<3.0.0,>=2.10.0
3
3
  exifread==2.3.2
4
4
  piexif==1.1.3
5
5
  gpxpy<1.6.0,>=1.5.0
6
- pynmea2==1.12.0
7
- requests<3.0.0,>=2.20.0
6
+ pynmea2<2.0.0,>=1.12.0
7
+ requests[socks]<3.0.0,>=2.20.0
8
8
  tqdm<5.0,>=4.0
9
9
  typing_extensions
10
10
  jsonschema~=4.17.3
@@ -3,8 +3,8 @@ construct>=2.10.0,<3.0.0
3
3
  exifread==2.3.2
4
4
  piexif==1.1.3
5
5
  gpxpy>=1.5.0,<1.6.0
6
- pynmea2==1.12.0
7
- requests>=2.20.0,<3.0.0
6
+ pynmea2>=1.12.0,<2.0.0
7
+ requests[socks]>=2.20.0,<3.0.0
8
8
  tqdm>=4.0,<5.0
9
9
  typing_extensions
10
10
  jsonschema~=4.17.3
@@ -1 +0,0 @@
1
- VERSION = "0.11.1"