mapillary-tools 0.13.3a1__py3-none-any.whl → 0.14.0a2__py3-none-any.whl

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 (83) hide show
  1. mapillary_tools/__init__.py +1 -1
  2. mapillary_tools/api_v4.py +237 -16
  3. mapillary_tools/authenticate.py +325 -64
  4. mapillary_tools/{geotag/blackvue_parser.py → blackvue_parser.py} +74 -54
  5. mapillary_tools/camm/camm_builder.py +55 -97
  6. mapillary_tools/camm/camm_parser.py +429 -181
  7. mapillary_tools/commands/__main__.py +12 -6
  8. mapillary_tools/commands/authenticate.py +8 -1
  9. mapillary_tools/commands/process.py +27 -51
  10. mapillary_tools/commands/process_and_upload.py +19 -5
  11. mapillary_tools/commands/sample_video.py +2 -3
  12. mapillary_tools/commands/upload.py +18 -9
  13. mapillary_tools/commands/video_process_and_upload.py +19 -5
  14. mapillary_tools/config.py +31 -13
  15. mapillary_tools/constants.py +47 -6
  16. mapillary_tools/exceptions.py +34 -35
  17. mapillary_tools/exif_read.py +221 -116
  18. mapillary_tools/exif_write.py +7 -7
  19. mapillary_tools/exiftool_read.py +33 -42
  20. mapillary_tools/exiftool_read_video.py +46 -33
  21. mapillary_tools/exiftool_runner.py +77 -0
  22. mapillary_tools/ffmpeg.py +24 -23
  23. mapillary_tools/geo.py +144 -120
  24. mapillary_tools/geotag/base.py +147 -0
  25. mapillary_tools/geotag/factory.py +291 -0
  26. mapillary_tools/geotag/geotag_images_from_exif.py +14 -131
  27. mapillary_tools/geotag/geotag_images_from_exiftool.py +126 -82
  28. mapillary_tools/geotag/geotag_images_from_gpx.py +53 -118
  29. mapillary_tools/geotag/geotag_images_from_gpx_file.py +13 -126
  30. mapillary_tools/geotag/geotag_images_from_nmea_file.py +4 -5
  31. mapillary_tools/geotag/geotag_images_from_video.py +53 -51
  32. mapillary_tools/geotag/geotag_videos_from_exiftool.py +97 -0
  33. mapillary_tools/geotag/geotag_videos_from_gpx.py +39 -0
  34. mapillary_tools/geotag/geotag_videos_from_video.py +20 -185
  35. mapillary_tools/geotag/image_extractors/base.py +18 -0
  36. mapillary_tools/geotag/image_extractors/exif.py +60 -0
  37. mapillary_tools/geotag/image_extractors/exiftool.py +18 -0
  38. mapillary_tools/geotag/options.py +160 -0
  39. mapillary_tools/geotag/utils.py +52 -16
  40. mapillary_tools/geotag/video_extractors/base.py +18 -0
  41. mapillary_tools/geotag/video_extractors/exiftool.py +70 -0
  42. mapillary_tools/{video_data_extraction/extractors/gpx_parser.py → geotag/video_extractors/gpx.py} +57 -39
  43. mapillary_tools/geotag/video_extractors/native.py +157 -0
  44. mapillary_tools/{geotag → gpmf}/gpmf_parser.py +205 -182
  45. mapillary_tools/{geotag → gpmf}/gps_filter.py +5 -3
  46. mapillary_tools/history.py +7 -13
  47. mapillary_tools/mp4/construct_mp4_parser.py +9 -8
  48. mapillary_tools/mp4/io_utils.py +0 -1
  49. mapillary_tools/mp4/mp4_sample_parser.py +36 -28
  50. mapillary_tools/mp4/simple_mp4_builder.py +10 -9
  51. mapillary_tools/mp4/simple_mp4_parser.py +13 -22
  52. mapillary_tools/process_geotag_properties.py +155 -392
  53. mapillary_tools/process_sequence_properties.py +562 -208
  54. mapillary_tools/sample_video.py +13 -20
  55. mapillary_tools/telemetry.py +26 -13
  56. mapillary_tools/types.py +111 -58
  57. mapillary_tools/upload.py +316 -298
  58. mapillary_tools/upload_api_v4.py +55 -122
  59. mapillary_tools/uploader.py +396 -254
  60. mapillary_tools/utils.py +42 -18
  61. {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0a2.dist-info}/METADATA +3 -2
  62. mapillary_tools-0.14.0a2.dist-info/RECORD +72 -0
  63. {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0a2.dist-info}/WHEEL +1 -1
  64. mapillary_tools/geotag/__init__.py +0 -1
  65. mapillary_tools/geotag/geotag_from_generic.py +0 -22
  66. mapillary_tools/geotag/geotag_images_from_exiftool_both_image_and_video.py +0 -93
  67. mapillary_tools/geotag/geotag_videos_from_exiftool_video.py +0 -145
  68. mapillary_tools/video_data_extraction/cli_options.py +0 -22
  69. mapillary_tools/video_data_extraction/extract_video_data.py +0 -176
  70. mapillary_tools/video_data_extraction/extractors/base_parser.py +0 -75
  71. mapillary_tools/video_data_extraction/extractors/blackvue_parser.py +0 -34
  72. mapillary_tools/video_data_extraction/extractors/camm_parser.py +0 -38
  73. mapillary_tools/video_data_extraction/extractors/exiftool_runtime_parser.py +0 -71
  74. mapillary_tools/video_data_extraction/extractors/exiftool_xml_parser.py +0 -53
  75. mapillary_tools/video_data_extraction/extractors/generic_video_parser.py +0 -52
  76. mapillary_tools/video_data_extraction/extractors/gopro_parser.py +0 -43
  77. mapillary_tools/video_data_extraction/extractors/nmea_parser.py +0 -24
  78. mapillary_tools/video_data_extraction/video_data_parser_factory.py +0 -39
  79. mapillary_tools-0.13.3a1.dist-info/RECORD +0 -75
  80. /mapillary_tools/{geotag → gpmf}/gpmf_gps_filter.py +0 -0
  81. {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0a2.dist-info}/entry_points.txt +0 -0
  82. {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0a2.dist-info/licenses}/LICENSE +0 -0
  83. {mapillary_tools-0.13.3a1.dist-info → mapillary_tools-0.14.0a2.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,5 @@
1
1
  # pyre-ignore-all-errors[5, 16, 21, 58]
2
+ from __future__ import annotations
2
3
 
3
4
  import typing as T
4
5
 
@@ -42,7 +43,7 @@ BoxType = T.Literal[
42
43
 
43
44
  class BoxDict(T.TypedDict, total=True):
44
45
  type: BoxType
45
- data: T.Union[T.Sequence["BoxDict"], T.Dict[str, T.Any], bytes]
46
+ data: T.Sequence["BoxDict"] | dict[str, T.Any] | bytes
46
47
 
47
48
 
48
49
  _UNITY_MATRIX = [0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000]
@@ -376,7 +377,7 @@ class Box64ConstructBuilder:
376
377
  NOTE: Do not build data with this struct. For building, use Box32StructBuilder instead.
377
378
  """
378
379
 
379
- _box: T.Optional[C.Construct]
380
+ _box: C.Construct | None
380
381
 
381
382
  def __init__(
382
383
  self,
@@ -438,7 +439,7 @@ class Box64ConstructBuilder:
438
439
  def parse_box(self, data: bytes) -> BoxDict:
439
440
  return T.cast(BoxDict, self.Box.parse(data))
440
441
 
441
- def parse_boxlist(self, data: bytes) -> T.List[BoxDict]:
442
+ def parse_boxlist(self, data: bytes) -> list[BoxDict]:
442
443
  return T.cast(T.List[BoxDict], self.BoxList.parse(data))
443
444
 
444
445
 
@@ -464,7 +465,7 @@ class Box32ConstructBuilder(Box64ConstructBuilder):
464
465
  def parse_box(self, data: bytes) -> BoxDict:
465
466
  raise NotImplementedError("Box32ConstructBuilder does not support parsing")
466
467
 
467
- def parse_boxlist(self, data: bytes) -> T.List[BoxDict]:
468
+ def parse_boxlist(self, data: bytes) -> list[BoxDict]:
468
469
  raise NotImplementedError("Box32ConstructBuilder does not support parsing")
469
470
 
470
471
  def build_box(self, box: BoxDict) -> bytes:
@@ -584,7 +585,7 @@ MOOVWithoutSTBLBuilderConstruct = Box32ConstructBuilder(
584
585
 
585
586
 
586
587
  def find_box_at_pathx(
587
- box: T.Union[T.Sequence[BoxDict], BoxDict], path: T.Sequence[bytes]
588
+ box: T.Sequence[BoxDict] | BoxDict, path: T.Sequence[bytes]
588
589
  ) -> BoxDict:
589
590
  found = find_box_at_path(box, path)
590
591
  if found is None:
@@ -593,8 +594,8 @@ def find_box_at_pathx(
593
594
 
594
595
 
595
596
  def find_box_at_path(
596
- box: T.Union[T.Sequence[BoxDict], BoxDict], path: T.Sequence[bytes]
597
- ) -> T.Optional[BoxDict]:
597
+ box: T.Sequence[BoxDict] | BoxDict, path: T.Sequence[bytes]
598
+ ) -> BoxDict | None:
598
599
  if not path:
599
600
  return None
600
601
 
@@ -608,7 +609,7 @@ def find_box_at_path(
608
609
  if box["type"] == path[0]:
609
610
  if len(path) == 1:
610
611
  return box
611
- box_data = T.cast(T.Sequence[BoxDict], box["data"])
612
+ box_data = T.cast(T.List[BoxDict], box["data"])
612
613
  # ListContainer from construct is not sequence
613
614
  assert isinstance(box_data, T.Sequence), (
614
615
  f"expect a list of boxes but got {type(box_data)} at path {path}"
@@ -3,7 +3,6 @@ import typing as T
3
3
 
4
4
 
5
5
  class ChainedIO(io.IOBase):
6
- # is the chained stream seekable?
7
6
  _streams: T.Sequence[io.IOBase]
8
7
  # the beginning offset of the current stream
9
8
  _begin_offset: int
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import datetime
2
4
  import typing as T
3
5
  from pathlib import Path
@@ -42,16 +44,16 @@ class Sample(T.NamedTuple):
42
44
  exact_timedelta: float
43
45
 
44
46
  # reference to the sample description
45
- description: T.Dict
47
+ description: dict
46
48
 
47
49
 
48
50
  def _extract_raw_samples(
49
51
  sizes: T.Sequence[int],
50
- chunk_entries: T.Sequence[T.Dict],
52
+ chunk_entries: T.Sequence[dict],
51
53
  chunk_offsets: T.Sequence[int],
52
54
  timedeltas: T.Sequence[int],
53
- composition_offsets: T.Optional[T.Sequence[int]],
54
- syncs: T.Optional[T.Set[int]],
55
+ composition_offsets: list[int] | None,
56
+ syncs: set[int] | None,
55
57
  ) -> T.Generator[RawSample, None, None]:
56
58
  if not sizes:
57
59
  return
@@ -128,7 +130,7 @@ def _extract_raw_samples(
128
130
 
129
131
  def _extract_samples(
130
132
  raw_samples: T.Iterator[RawSample],
131
- descriptions: T.List,
133
+ descriptions: list,
132
134
  timescale: int,
133
135
  ) -> T.Generator[Sample, None, None]:
134
136
  acc_delta = 0
@@ -152,21 +154,21 @@ STBLBoxlistConstruct = cparser.Box64ConstructBuilder(
152
154
 
153
155
  def extract_raw_samples_from_stbl_data(
154
156
  stbl: bytes,
155
- ) -> T.Tuple[T.List[T.Dict], T.Generator[RawSample, None, None]]:
156
- descriptions = []
157
- sizes = []
158
- chunk_offsets = []
159
- chunk_entries = []
160
- timedeltas: T.List[int] = []
161
- composition_offsets: T.Optional[T.List[int]] = None
162
- syncs: T.Optional[T.Set[int]] = None
157
+ ) -> tuple[list[dict], T.Generator[RawSample, None, None]]:
158
+ descriptions: list[dict] = []
159
+ sizes: list[int] = []
160
+ chunk_offsets: list[int] = []
161
+ chunk_entries: list[dict] = []
162
+ timedeltas: list[int] = []
163
+ composition_offsets: list[int] | None = None
164
+ syncs: set[int] | None = None
163
165
 
164
166
  stbl_children = T.cast(
165
167
  T.Sequence[cparser.BoxDict], STBLBoxlistConstruct.parse(stbl)
166
168
  )
167
169
 
168
170
  for box in stbl_children:
169
- data: T.Dict = T.cast(T.Dict, box["data"])
171
+ data: dict = T.cast(dict, box["data"])
170
172
 
171
173
  if box["type"] == b"stsd":
172
174
  descriptions = list(data["entries"])
@@ -225,32 +227,32 @@ class TrackBoxParser:
225
227
  )
226
228
  self.stbl_data = T.cast(bytes, stbl["data"])
227
229
 
228
- def extract_tkhd_boxdata(self) -> T.Dict:
230
+ def extract_tkhd_boxdata(self) -> dict:
229
231
  return T.cast(
230
- T.Dict, cparser.find_box_at_pathx(self.trak_children, [b"tkhd"])["data"]
232
+ dict, cparser.find_box_at_pathx(self.trak_children, [b"tkhd"])["data"]
231
233
  )
232
234
 
233
235
  def is_video_track(self) -> bool:
234
236
  hdlr = cparser.find_box_at_pathx(self.trak_children, [b"mdia", b"hdlr"])
235
237
  return T.cast(T.Dict[str, T.Any], hdlr["data"])["handler_type"] == b"vide"
236
238
 
237
- def extract_sample_descriptions(self) -> T.List[T.Dict]:
239
+ def extract_sample_descriptions(self) -> list[dict]:
238
240
  # TODO: return [] if parsing fail
239
241
  boxes = _STSDBoxListConstruct.parse(self.stbl_data)
240
242
  stsd = cparser.find_box_at_pathx(
241
243
  T.cast(T.Sequence[cparser.BoxDict], boxes), [b"stsd"]
242
244
  )
243
- return T.cast(T.List[T.Dict], T.cast(T.Dict, stsd["data"])["entries"])
245
+ return T.cast(T.List[dict], T.cast(dict, stsd["data"])["entries"])
244
246
 
245
- def extract_elst_boxdata(self) -> T.Optional[T.Dict]:
247
+ def extract_elst_boxdata(self) -> dict | None:
246
248
  box = cparser.find_box_at_path(self.trak_children, [b"edts", b"elst"])
247
249
  if box is None:
248
250
  return None
249
- return T.cast(T.Dict, box["data"])
251
+ return T.cast(dict, box["data"])
250
252
 
251
- def extract_mdhd_boxdata(self) -> T.Dict:
253
+ def extract_mdhd_boxdata(self) -> dict:
252
254
  box = cparser.find_box_at_pathx(self.trak_children, [b"mdia", b"mdhd"])
253
- return T.cast(T.Dict, box["data"])
255
+ return T.cast(dict, box["data"])
254
256
 
255
257
  def extract_raw_samples(self) -> T.Generator[RawSample, None, None]:
256
258
  _, raw_samples = extract_raw_samples_from_stbl_data(self.stbl_data)
@@ -259,7 +261,7 @@ class TrackBoxParser:
259
261
  def extract_samples(self) -> T.Generator[Sample, None, None]:
260
262
  descriptions, raw_samples = extract_raw_samples_from_stbl_data(self.stbl_data)
261
263
  mdhd = T.cast(
262
- T.Dict,
264
+ dict,
263
265
  cparser.find_box_at_pathx(self.trak_children, [b"mdia", b"mdhd"])["data"],
264
266
  )
265
267
  yield from _extract_samples(raw_samples, descriptions, mdhd["timescale"])
@@ -278,16 +280,22 @@ class MovieBoxParser:
278
280
  def parse_file(cls, video_path: Path) -> "MovieBoxParser":
279
281
  with video_path.open("rb") as fp:
280
282
  moov = sparser.parse_box_data_firstx(fp, [b"moov"])
281
- return MovieBoxParser(moov)
283
+ return cls(moov)
282
284
 
283
285
  @classmethod
284
286
  def parse_stream(cls, stream: T.BinaryIO) -> "MovieBoxParser":
285
287
  moov = sparser.parse_box_data_firstx(stream, [b"moov"])
286
- return MovieBoxParser(moov)
288
+ return cls(moov)
287
289
 
288
- def extract_mvhd_boxdata(self) -> T.Dict:
290
+ def extract_mvhd_boxdata(self) -> dict:
289
291
  mvhd = cparser.find_box_at_pathx(self.moov_children, [b"mvhd"])
290
- return T.cast(T.Dict, mvhd["data"])
292
+ return T.cast(dict, mvhd["data"])
293
+
294
+ def extract_udta_boxdata(self) -> dict | None:
295
+ box = cparser.find_box_at_path(self.moov_children, [b"udta"])
296
+ if box is None:
297
+ return None
298
+ return T.cast(dict, box["data"])
291
299
 
292
300
  def extract_tracks(self) -> T.Generator[TrackBoxParser, None, None]:
293
301
  for box in self.moov_children:
@@ -312,7 +320,7 @@ class MovieBoxParser:
312
320
  return TrackBoxParser(trak_children)
313
321
 
314
322
 
315
- _DT_1904 = datetime.datetime.utcfromtimestamp(0).replace(year=1904)
323
+ _DT_1904 = datetime.datetime.fromtimestamp(0, datetime.timezone.utc).replace(year=1904)
316
324
 
317
325
 
318
326
  def to_datetime(seconds_since_1904: int) -> datetime.datetime:
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import dataclasses
2
4
  import io
3
5
  import typing as T
@@ -64,8 +66,8 @@ class _SampleChunk:
64
66
  offset: int
65
67
 
66
68
 
67
- def _build_chunks(raw_samples: T.Iterable[RawSample]) -> T.List[_SampleChunk]:
68
- chunks: T.List[_SampleChunk] = []
69
+ def _build_chunks(raw_samples: T.Iterable[RawSample]) -> list[_SampleChunk]:
70
+ chunks: list[_SampleChunk] = []
69
71
  prev_raw_sample = None
70
72
 
71
73
  for raw_sample in raw_samples:
@@ -120,7 +122,7 @@ class _CompressedSampleDelta:
120
122
 
121
123
  def _build_stts(sample_deltas: T.Iterable[int]) -> BoxDict:
122
124
  # compress deltas
123
- compressed: T.List[_CompressedSampleDelta] = []
125
+ compressed: list[_CompressedSampleDelta] = []
124
126
  for delta in sample_deltas:
125
127
  if compressed and delta == compressed[-1].sample_delta:
126
128
  compressed[-1].sample_count += 1
@@ -146,7 +148,7 @@ class _CompressedSampleCompositionOffset:
146
148
 
147
149
  def _build_ctts(sample_composition_offsets: T.Iterable[int]) -> BoxDict:
148
150
  # compress offsets
149
- compressed: T.List[_CompressedSampleCompositionOffset] = []
151
+ compressed: list[_CompressedSampleCompositionOffset] = []
150
152
  for offset in sample_composition_offsets:
151
153
  if compressed and offset == compressed[-1].sample_offset:
152
154
  compressed[-1].sample_count += 1
@@ -182,7 +184,7 @@ def _build_stss(is_syncs: T.Iterable[bool]) -> BoxDict:
182
184
 
183
185
  def build_stbl_from_raw_samples(
184
186
  descriptions: T.Sequence[T.Any], raw_samples: T.Iterable[RawSample]
185
- ) -> T.List[BoxDict]:
187
+ ) -> list[BoxDict]:
186
188
  # raw_samples could be iterator so convert to list
187
189
  raw_samples = list(raw_samples)
188
190
  # It is recommended that the boxes within the Sample Table Box be in the following order:
@@ -329,9 +331,8 @@ _MOOVChildrenParserConstruct = cparser.Box64ConstructBuilder(
329
331
 
330
332
  def transform_mp4(
331
333
  src_fp: T.BinaryIO,
332
- sample_generator: T.Optional[
333
- T.Callable[[T.BinaryIO, T.List[BoxDict]], T.Iterator[io.IOBase]]
334
- ] = None,
334
+ sample_generator: T.Callable[[T.BinaryIO, list[BoxDict]], T.Iterator[io.IOBase]]
335
+ | None = None,
335
336
  ) -> io_utils.ChainedIO:
336
337
  # extract ftyp
337
338
  src_fp.seek(0)
@@ -347,7 +348,7 @@ def transform_mp4(
347
348
 
348
349
  # extract video samples
349
350
  source_samples = list(iterate_samples(moov_children))
350
- sample_readers: T.List[io.IOBase] = [
351
+ sample_readers: list[io.IOBase] = [
351
352
  io_utils.SlicedIO(src_fp, sample.offset, sample.size)
352
353
  for sample in source_samples
353
354
  ]
@@ -1,4 +1,5 @@
1
1
  # pyre-ignore-all-errors[5, 16, 21, 24, 58]
2
+ from __future__ import annotations
2
3
 
3
4
  import io
4
5
  import typing as T
@@ -130,8 +131,8 @@ def parse_boxes_recursive(
130
131
  stream: T.BinaryIO,
131
132
  maxsize: int = -1,
132
133
  depth: int = 0,
133
- box_list_types: T.Optional[T.Set[bytes]] = None,
134
- ) -> T.Generator[T.Tuple[Header, int, T.BinaryIO], None, None]:
134
+ box_list_types: set[bytes] | None = None,
135
+ ) -> T.Generator[tuple[Header, int, T.BinaryIO], None, None]:
135
136
  assert maxsize == -1 or 0 <= maxsize
136
137
 
137
138
  if box_list_types is None:
@@ -152,10 +153,10 @@ def parse_boxes_recursive(
152
153
 
153
154
  def parse_path(
154
155
  stream: T.BinaryIO,
155
- path: T.Sequence[T.Union[bytes, T.Sequence[bytes]]],
156
+ path: T.Sequence[bytes | T.Sequence[bytes]],
156
157
  maxsize: int = -1,
157
158
  depth: int = 0,
158
- ) -> T.Generator[T.Tuple[Header, T.BinaryIO], None, None]:
159
+ ) -> T.Generator[tuple[Header, T.BinaryIO], None, None]:
159
160
  if not path:
160
161
  return
161
162
 
@@ -172,8 +173,8 @@ def parse_path(
172
173
 
173
174
 
174
175
  def _parse_path_first(
175
- stream: T.BinaryIO, path: T.List[bytes], maxsize: int = -1, depth: int = 0
176
- ) -> T.Optional[T.Tuple[Header, T.BinaryIO]]:
176
+ stream: T.BinaryIO, path: list[bytes], maxsize: int = -1, depth: int = 0
177
+ ) -> tuple[Header, T.BinaryIO] | None:
177
178
  if not path:
178
179
  return None
179
180
  for h, s in parse_boxes(stream, maxsize=maxsize, extend_eof=depth == 0):
@@ -187,19 +188,9 @@ def _parse_path_first(
187
188
  return None
188
189
 
189
190
 
190
- def parse_box_path_firstx(
191
- stream: T.BinaryIO, path: T.List[bytes], maxsize: int = -1
192
- ) -> T.Tuple[Header, T.BinaryIO]:
193
- # depth=1 will disable EoF extension
194
- parsed = _parse_path_first(stream, path, maxsize=maxsize, depth=1)
195
- if parsed is None:
196
- raise BoxNotFoundError(f"unable find box at path {path}")
197
- return parsed
198
-
199
-
200
191
  def parse_mp4_data_first(
201
- stream: T.BinaryIO, path: T.List[bytes], maxsize: int = -1
202
- ) -> T.Optional[bytes]:
192
+ stream: T.BinaryIO, path: list[bytes], maxsize: int = -1
193
+ ) -> bytes | None:
203
194
  # depth=0 will enable EoF extension
204
195
  parsed = _parse_path_first(stream, path, maxsize=maxsize, depth=0)
205
196
  if parsed is None:
@@ -209,7 +200,7 @@ def parse_mp4_data_first(
209
200
 
210
201
 
211
202
  def parse_mp4_data_firstx(
212
- stream: T.BinaryIO, path: T.List[bytes], maxsize: int = -1
203
+ stream: T.BinaryIO, path: list[bytes], maxsize: int = -1
213
204
  ) -> bytes:
214
205
  data = parse_mp4_data_first(stream, path, maxsize=maxsize)
215
206
  if data is None:
@@ -218,8 +209,8 @@ def parse_mp4_data_firstx(
218
209
 
219
210
 
220
211
  def parse_box_data_first(
221
- stream: T.BinaryIO, path: T.List[bytes], maxsize: int = -1
222
- ) -> T.Optional[bytes]:
212
+ stream: T.BinaryIO, path: list[bytes], maxsize: int = -1
213
+ ) -> bytes | None:
223
214
  # depth=1 will disable EoF extension
224
215
  parsed = _parse_path_first(stream, path, maxsize=maxsize, depth=1)
225
216
  if parsed is None:
@@ -229,7 +220,7 @@ def parse_box_data_first(
229
220
 
230
221
 
231
222
  def parse_box_data_firstx(
232
- stream: T.BinaryIO, path: T.List[bytes], maxsize: int = -1
223
+ stream: T.BinaryIO, path: list[bytes], maxsize: int = -1
233
224
  ) -> bytes:
234
225
  data = parse_box_data_first(stream, path, maxsize=maxsize)
235
226
  if data is None: