mapillary-tools 0.12.1__py3-none-any.whl → 0.13.1__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 (59) hide show
  1. mapillary_tools/__init__.py +1 -1
  2. mapillary_tools/api_v4.py +94 -4
  3. mapillary_tools/{geotag → camm}/camm_builder.py +73 -61
  4. mapillary_tools/camm/camm_parser.py +561 -0
  5. mapillary_tools/commands/__init__.py +0 -1
  6. mapillary_tools/commands/__main__.py +0 -6
  7. mapillary_tools/commands/process.py +0 -50
  8. mapillary_tools/commands/upload.py +1 -26
  9. mapillary_tools/constants.py +2 -2
  10. mapillary_tools/exiftool_read_video.py +13 -11
  11. mapillary_tools/ffmpeg.py +2 -2
  12. mapillary_tools/geo.py +0 -54
  13. mapillary_tools/geotag/blackvue_parser.py +4 -4
  14. mapillary_tools/geotag/geotag_images_from_exif.py +2 -1
  15. mapillary_tools/geotag/geotag_images_from_exiftool_both_image_and_video.py +0 -1
  16. mapillary_tools/geotag/geotag_images_from_gpx_file.py +7 -1
  17. mapillary_tools/geotag/geotag_videos_from_exiftool_video.py +5 -3
  18. mapillary_tools/geotag/geotag_videos_from_video.py +13 -14
  19. mapillary_tools/geotag/gpmf_gps_filter.py +9 -10
  20. mapillary_tools/geotag/gpmf_parser.py +346 -83
  21. mapillary_tools/mp4/__init__.py +0 -0
  22. mapillary_tools/{geotag → mp4}/construct_mp4_parser.py +32 -16
  23. mapillary_tools/mp4/mp4_sample_parser.py +322 -0
  24. mapillary_tools/{geotag → mp4}/simple_mp4_builder.py +64 -38
  25. mapillary_tools/process_geotag_properties.py +25 -19
  26. mapillary_tools/process_sequence_properties.py +6 -6
  27. mapillary_tools/sample_video.py +17 -16
  28. mapillary_tools/telemetry.py +71 -0
  29. mapillary_tools/types.py +18 -0
  30. mapillary_tools/upload.py +74 -233
  31. mapillary_tools/upload_api_v4.py +8 -9
  32. mapillary_tools/utils.py +9 -16
  33. mapillary_tools/video_data_extraction/cli_options.py +0 -1
  34. mapillary_tools/video_data_extraction/extract_video_data.py +13 -31
  35. mapillary_tools/video_data_extraction/extractors/base_parser.py +13 -11
  36. mapillary_tools/video_data_extraction/extractors/blackvue_parser.py +5 -4
  37. mapillary_tools/video_data_extraction/extractors/camm_parser.py +13 -16
  38. mapillary_tools/video_data_extraction/extractors/exiftool_runtime_parser.py +4 -9
  39. mapillary_tools/video_data_extraction/extractors/exiftool_xml_parser.py +9 -11
  40. mapillary_tools/video_data_extraction/extractors/generic_video_parser.py +6 -11
  41. mapillary_tools/video_data_extraction/extractors/gopro_parser.py +11 -4
  42. mapillary_tools/video_data_extraction/extractors/gpx_parser.py +90 -11
  43. mapillary_tools/video_data_extraction/extractors/nmea_parser.py +3 -3
  44. mapillary_tools/video_data_extraction/video_data_parser_factory.py +13 -20
  45. {mapillary_tools-0.12.1.dist-info → mapillary_tools-0.13.1.dist-info}/METADATA +10 -3
  46. mapillary_tools-0.13.1.dist-info/RECORD +75 -0
  47. {mapillary_tools-0.12.1.dist-info → mapillary_tools-0.13.1.dist-info}/WHEEL +1 -1
  48. mapillary_tools/commands/upload_blackvue.py +0 -33
  49. mapillary_tools/commands/upload_camm.py +0 -33
  50. mapillary_tools/commands/upload_zip.py +0 -33
  51. mapillary_tools/geotag/camm_parser.py +0 -306
  52. mapillary_tools/geotag/mp4_sample_parser.py +0 -426
  53. mapillary_tools/process_import_meta_properties.py +0 -76
  54. mapillary_tools-0.12.1.dist-info/RECORD +0 -77
  55. /mapillary_tools/{geotag → mp4}/io_utils.py +0 -0
  56. /mapillary_tools/{geotag → mp4}/simple_mp4_parser.py +0 -0
  57. {mapillary_tools-0.12.1.dist-info → mapillary_tools-0.13.1.dist-info}/LICENSE +0 -0
  58. {mapillary_tools-0.12.1.dist-info → mapillary_tools-0.13.1.dist-info}/entry_points.txt +0 -0
  59. {mapillary_tools-0.12.1.dist-info → mapillary_tools-0.13.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,75 @@
1
+ mapillary_tools/__init__.py,sha256=XBFY-aPf1nqJ7U8CvnYiXTRu3uDJLRQjOxibBTi-54U,19
2
+ mapillary_tools/api_v4.py,sha256=zhRtgx3EnzgqtjziRhvFq3ONvsPaB9hROsuKFcf_pFo,5197
3
+ mapillary_tools/authenticate.py,sha256=LCFcs6LqZmXaYkTUEKgGfmqytWdh5v_L3KXB48ojOZ4,3090
4
+ mapillary_tools/config.py,sha256=jCjaK4jJaTY4AV4qf_b_tcxn5LA_uPsEWlGIdm2zw6g,2103
5
+ mapillary_tools/constants.py,sha256=CZpKd9wUxlTC3lq7h3Mi5J1NpPlLYvgp1r0_sFUJKGk,2452
6
+ mapillary_tools/exceptions.py,sha256=Mh1tgVEFTSMnYEzrl9x7b95fW9Z3SPVD_YMEl7r8I0I,2693
7
+ mapillary_tools/exif_read.py,sha256=F60A0-T8XSBHvFKgVIrUz_ZWKQrTFWrtj3c6siB0IMg,28707
8
+ mapillary_tools/exif_write.py,sha256=3PawLnBOY8Z86TYiA_F4LxRhe5Ui6CTNhxYm9yeJNX8,8786
9
+ mapillary_tools/exiftool_read.py,sha256=Mg027me1IzblKb9IyGaLgU6goFqk_QiOt0Ppq-CeECg,16288
10
+ mapillary_tools/exiftool_read_video.py,sha256=f3l8HiDjkrIhmslIXSsC8cNgkqCVWUFkN_0OQ5ZUd-U,14431
11
+ mapillary_tools/ffmpeg.py,sha256=p1a5VxlbpLGLkzulMv51bpyD5omwK7Qg8277TGYmcZA,15780
12
+ mapillary_tools/geo.py,sha256=QybCiQr0UDcH6DIDa2L6cZw4sDoTJNgR99oo6S7gCts,9746
13
+ mapillary_tools/history.py,sha256=l2z3YdYRyBGEOvcqcLExTV-0LUAX3iBq2OdBzLNMHLM,1766
14
+ mapillary_tools/ipc.py,sha256=DwWQb9hNshx0bg0Fo5NjY0mXjs-FkbR6tIQmjMgMtmg,1089
15
+ mapillary_tools/process_geotag_properties.py,sha256=w4hhv_c4sRydCK9QCO50sT2yo2zeVlY7dSdXQ93InFc,23159
16
+ mapillary_tools/process_sequence_properties.py,sha256=5oYEjz9crnLVQtCkxbwn57TkeuHFbBh_zQXQSA4ENWg,11561
17
+ mapillary_tools/sample_video.py,sha256=dpdX7bUNEmcrz-3gh3Y3awnTDX66pChbTKuF8qGfeCI,14400
18
+ mapillary_tools/telemetry.py,sha256=3a7MvTH4Rr_mXp2NInubPIS8JFOuBeQC7PC2prfXNfI,1559
19
+ mapillary_tools/types.py,sha256=6kww2UdKM6YzabYbc862BYzEWtxL2hhxCRFfeDiUtF0,22074
20
+ mapillary_tools/upload.py,sha256=ab1WEut6qv352njWKtNbUXmV8KShlApOjcwb1FpcC_Q,24439
21
+ mapillary_tools/upload_api_v4.py,sha256=1WvoUis92KDXbqfoyvyyDmiCqwXezYkMJZhnYaVm3BA,8560
22
+ mapillary_tools/uploader.py,sha256=VieDKi51wdXTIhN7x_mcuQeHESUyFlF5cgB-TAnF4g0,14093
23
+ mapillary_tools/utils.py,sha256=VNtK1tAb3Hh8y3P5e5Y3iewREkIoLDa3C2myRYcF2lY,5970
24
+ mapillary_tools/camm/camm_builder.py,sha256=x4WF-n6zOEUhK1Poo7du004-7DQhjnf598ZNfM3r7iA,9075
25
+ mapillary_tools/camm/camm_parser.py,sha256=ARwImVcXY1FckCnPLPoyYAuODJb5iZI0hlQurAIeiWQ,16473
26
+ mapillary_tools/commands/__init__.py,sha256=41CFrPLGlG3566uhxssEF3TGAtSpADFPPcDMHbViU0E,171
27
+ mapillary_tools/commands/__main__.py,sha256=VdWkx1ekPH-88Ybe78IcO9FWpZ5cUhsbGRw7LuzQObU,4832
28
+ mapillary_tools/commands/authenticate.py,sha256=4aVvAQal_mqtm2NEMBt5aKLahi0iRdO8b7WSBf6jokA,1136
29
+ mapillary_tools/commands/process.py,sha256=VxcvQpYHPw7QfT9dNwBLV1jWQ-1w4GtVNVPpmu4Sx9s,10578
30
+ mapillary_tools/commands/process_and_upload.py,sha256=osJv1TVHYAG5E-EsA0nB1F3RkKXMadLR2t1EGK0Ifqw,654
31
+ mapillary_tools/commands/sample_video.py,sha256=bTJmlDsajkC-QJ_ZO_scdD4R664zs-r_dh-x2PlOgyY,3281
32
+ mapillary_tools/commands/upload.py,sha256=JIWgxupV3ppLvPi1iE7UVaE1302JGcIOvnuNt1Y7YEw,1671
33
+ mapillary_tools/commands/video_process.py,sha256=-wQeeIwWXPmy81HQHam5A0huMLRHknkEFa_V1OwElU4,890
34
+ mapillary_tools/commands/video_process_and_upload.py,sha256=llV0dHBS31qPZp-Fs1GCM0yYezEA_VF_tfYcp-Z8NkY,701
35
+ mapillary_tools/commands/zip.py,sha256=DVQuMLpbstwiy5o4pU_fBvM6eORuFjLeySd80AhHKU0,991
36
+ mapillary_tools/geotag/__init__.py,sha256=ohud7lLsqO1b9ddCF0SjrNOcUcRdQzNVR43RkcVVLAc,33
37
+ mapillary_tools/geotag/blackvue_parser.py,sha256=_LTI_biiznFPvrk5dcpoDH4tP4_7khPIpW5Daumuf68,2968
38
+ mapillary_tools/geotag/geotag_from_generic.py,sha256=bCYfIbkv4qIlkKttAjSGl9t4i_QAtzIiCZoth1hMVI4,480
39
+ mapillary_tools/geotag/geotag_images_from_exif.py,sha256=hCgBwZABk2tbBQC3cHQBV5pvNwlAo8AkWSgCD0BU_QU,4823
40
+ mapillary_tools/geotag/geotag_images_from_exiftool.py,sha256=a-c4H8VIyPdJkfUIvJho0phR0QU0zN8-lSyiCz0wc4s,3981
41
+ mapillary_tools/geotag/geotag_images_from_exiftool_both_image_and_video.py,sha256=nRVAjgTJwx_eCaSBpPCgcIaZs3EYgGueYxSS9XhKv40,3350
42
+ mapillary_tools/geotag/geotag_images_from_gpx.py,sha256=S9Pw6FvP5kRSpHUnKUYKXmw0CHa9V92UmrS_MJfbjS4,9053
43
+ mapillary_tools/geotag/geotag_images_from_gpx_file.py,sha256=-vTbZ1HufZzJCd8VvukdTjsJRcymtfld2W5t65VSG5E,5300
44
+ mapillary_tools/geotag/geotag_images_from_nmea_file.py,sha256=dDdHnJInQ_WN3ZRf-w44NSBElDLPs7XYBiimvE2iCNo,1651
45
+ mapillary_tools/geotag/geotag_images_from_video.py,sha256=XsaWOFChGItl-j1UbKM4hNjUqN29pVNbMpGT_BvI-o8,3306
46
+ mapillary_tools/geotag/geotag_videos_from_exiftool_video.py,sha256=fkkWou1WFt3ft024399vis9No2cxrwot7Pg5HBw7o7s,5225
47
+ mapillary_tools/geotag/geotag_videos_from_video.py,sha256=mqBZKUEkqT96nOzl5LJxzzTKuKsnAkMK5lH8k3oY3YE,7330
48
+ mapillary_tools/geotag/gpmf_gps_filter.py,sha256=7cg8wEjC1DrujKY76FZguXsaPqTRkG9-t32OeuOJQIc,2755
49
+ mapillary_tools/geotag/gpmf_parser.py,sha256=6Q38oeSd2kHTs44Fzpg9R555EbQqdd5QcIuIZdG4z_o,23233
50
+ mapillary_tools/geotag/gps_filter.py,sha256=4CPL8glxdzPWIbfGPPgyqMLyiZFt-djce5vhiFPuZB8,3766
51
+ mapillary_tools/geotag/utils.py,sha256=Orl35Df4ypxj4v6Lu1Mhk9d2XZxa8yNffz1s1C0JZsQ,651
52
+ mapillary_tools/mp4/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
+ mapillary_tools/mp4/construct_mp4_parser.py,sha256=31oJbg6umKJjdZ0Ni5eCudC0lXd0Y-K_EiCCj6yW9js,17199
54
+ mapillary_tools/mp4/io_utils.py,sha256=wc3-F1TnxZjTwB7-oea5yRmRQ_0T3Zbz8oBkW9JL8d4,5454
55
+ mapillary_tools/mp4/mp4_sample_parser.py,sha256=YnTIIHGHL3ViLo_Ap0C2hk8MDWbWtvSXBLD42pRIWqY,11337
56
+ mapillary_tools/mp4/simple_mp4_builder.py,sha256=7zVepmW-2SwoAeA-fvucmBW94jlcCFExDcEg8P3TOGY,12727
57
+ mapillary_tools/mp4/simple_mp4_parser.py,sha256=eji6JZa497wK8CY8hQt21fjgtnd0nzuyBx7MPEKST74,6671
58
+ mapillary_tools/video_data_extraction/cli_options.py,sha256=N0uHi9Uzaw1C8N-PE3yu8J3uEQP3HvSjJ9AZbIqoREg,535
59
+ mapillary_tools/video_data_extraction/extract_video_data.py,sha256=_2BBdSYeYKR4BCHAZa1Jzo7OIK_va1lJDkTU2sXsPc0,6000
60
+ mapillary_tools/video_data_extraction/video_data_parser_factory.py,sha256=qaJHvLgwI5lukJncMd8ggxeSxXOiVzBSJO5GlGQYiXY,1134
61
+ mapillary_tools/video_data_extraction/extractors/base_parser.py,sha256=s7Xuwg4I5JZ27oL4ebMSdo093plAXfZ-6uDQ_h97WHY,2134
62
+ mapillary_tools/video_data_extraction/extractors/blackvue_parser.py,sha256=jAcGyF6PML2EdJ4zle8cR12QeTRZc5qxlz8_4gcTZPU,1089
63
+ mapillary_tools/video_data_extraction/extractors/camm_parser.py,sha256=YMiViocXSVlfn8_qm1jcwSJhnnEaK8v5ADHwo2YXe10,1117
64
+ mapillary_tools/video_data_extraction/extractors/exiftool_runtime_parser.py,sha256=PFNCRk9pGrPIfVwLMcnzmVNMITVjNHhbrOOMwxaSstg,2270
65
+ mapillary_tools/video_data_extraction/extractors/exiftool_xml_parser.py,sha256=Tt0h4TiCKocERWMlRXzlpoaA_WJ_4b20MgMLGYNl4AM,1734
66
+ mapillary_tools/video_data_extraction/extractors/generic_video_parser.py,sha256=34O6Km5kNDoJNJtIUOwtAzzMntuqkSZJfeli7caWSkA,1693
67
+ mapillary_tools/video_data_extraction/extractors/gopro_parser.py,sha256=IVnTyquSraTUaG9rxbJfVWc1-drdY5PaHn5urh3IBk4,1325
68
+ mapillary_tools/video_data_extraction/extractors/gpx_parser.py,sha256=FNrdnXl48k8I1I5fGwYsClhfFEHVsooRLRboUYECv3I,3811
69
+ mapillary_tools/video_data_extraction/extractors/nmea_parser.py,sha256=raSXavBvP-0LJCB_TwLL0mOv2uHSsB744igTsaKAaGc,658
70
+ mapillary_tools-0.13.1.dist-info/LICENSE,sha256=l2D8cKfFmmJq_wcVq_JElPJrlvWQOzNWx7gMLINucxc,1292
71
+ mapillary_tools-0.13.1.dist-info/METADATA,sha256=K1pmlXXrkP_o1qFddzeG6RyXGt6HqVKhU8aLhIVUK_w,19758
72
+ mapillary_tools-0.13.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
73
+ mapillary_tools-0.13.1.dist-info/entry_points.txt,sha256=A3f3LP-BO_P-U8Y29QfpT4jx6Mjk3sXjTi2Yew4bvj8,75
74
+ mapillary_tools-0.13.1.dist-info/top_level.txt,sha256=FbDkMgOrt1S70ho1WSBrOwzKOSkJFDwwqFOoY5-527s,16
75
+ mapillary_tools-0.13.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.6.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,33 +0,0 @@
1
- import inspect
2
- from pathlib import Path
3
-
4
- from .. import constants, upload
5
- from .upload import Command as UploadCommand
6
-
7
-
8
- class Command:
9
- name = "upload_blackvue"
10
- help = "[deprecated] upload BlackVue videos to Mapillary"
11
-
12
- def add_basic_arguments(self, parser):
13
- parser.add_argument(
14
- "import_path",
15
- help="Path to your BlackVue videos.",
16
- nargs="+",
17
- type=Path,
18
- )
19
- group = parser.add_argument_group(
20
- f"{constants.ANSI_BOLD}UPLOAD OPTIONS{constants.ANSI_RESET_ALL}"
21
- )
22
- UploadCommand.add_common_upload_options(group)
23
-
24
- def run(self, vars_args: dict):
25
- args = {
26
- k: v
27
- for k, v in vars_args.items()
28
- if k in inspect.getfullargspec(upload.upload).args
29
- }
30
- upload.upload(
31
- **args,
32
- filetypes={upload.DirectUploadFileType.RAW_BLACKVUE},
33
- )
@@ -1,33 +0,0 @@
1
- import inspect
2
- from pathlib import Path
3
-
4
- from .. import constants, upload
5
- from .upload import Command as UploadCommand
6
-
7
-
8
- class Command:
9
- name = "upload_camm"
10
- help = "[deprecated] upload CAMM videos to Mapillary"
11
-
12
- def add_basic_arguments(self, parser):
13
- parser.add_argument(
14
- "import_path",
15
- help="Path to your CAMM videos.",
16
- nargs="+",
17
- type=Path,
18
- )
19
- group = parser.add_argument_group(
20
- f"{constants.ANSI_BOLD}UPLOAD OPTIONS{constants.ANSI_RESET_ALL}"
21
- )
22
- UploadCommand.add_common_upload_options(group)
23
-
24
- def run(self, vars_args: dict):
25
- args = {
26
- k: v
27
- for k, v in vars_args.items()
28
- if k in inspect.getfullargspec(upload.upload).args
29
- }
30
- upload.upload(
31
- **args,
32
- filetypes={upload.DirectUploadFileType.RAW_CAMM},
33
- )
@@ -1,33 +0,0 @@
1
- import inspect
2
- from pathlib import Path
3
-
4
- from .. import constants, upload
5
- from .upload import Command as UploadCommand
6
-
7
-
8
- class Command:
9
- name = "upload_zip"
10
- help = "[deprecated] upload ZIP files to Mapillary"
11
-
12
- def add_basic_arguments(self, parser):
13
- parser.add_argument(
14
- "import_path",
15
- help="Path to your ZIP files.",
16
- nargs="+",
17
- type=Path,
18
- )
19
- group = parser.add_argument_group(
20
- f"{constants.ANSI_BOLD}UPLOAD OPTIONS{constants.ANSI_RESET_ALL}"
21
- )
22
- UploadCommand.add_common_upload_options(group)
23
-
24
- def run(self, vars_args: dict):
25
- args = {
26
- k: v
27
- for k, v in vars_args.items()
28
- if k in inspect.getfullargspec(upload.upload).args
29
- }
30
- upload.upload(
31
- **args,
32
- filetypes={upload.DirectUploadFileType.ZIP},
33
- )
@@ -1,306 +0,0 @@
1
- # pyre-ignore-all-errors[5, 11, 16, 21, 24, 58]
2
-
3
- import dataclasses
4
- import io
5
- import logging
6
- import pathlib
7
- import typing as T
8
- from enum import Enum
9
-
10
- import construct as C
11
-
12
- from . import (
13
- construct_mp4_parser as cparser,
14
- geo,
15
- mp4_sample_parser as sample_parser,
16
- simple_mp4_parser as parser,
17
- )
18
-
19
-
20
- LOG = logging.getLogger(__name__)
21
-
22
-
23
- # Camera Motion Metadata Spec https://developers.google.com/streetview/publish/camm-spec
24
- class CAMMType(Enum):
25
- ANGLE_AXIS = 0
26
- EXPOSURE_TIME = 1
27
- GYRO = 2
28
- ACCELERATION = 3
29
- POSITION = 4
30
- MIN_GPS = 5
31
- GPS = 6
32
- MAGNETIC_FIELD = 7
33
-
34
-
35
- # All fields are little-endian
36
- Float = C.Float32l
37
- Double = C.Float64l
38
-
39
- _SWITCH: T.Dict[int, C.Struct] = {
40
- # angle_axis
41
- CAMMType.ANGLE_AXIS.value: Float[3],
42
- CAMMType.EXPOSURE_TIME.value: C.Struct(
43
- "pixel_exposure_time" / C.Int32sl,
44
- "rolling_shutter_skew_time" / C.Int32sl,
45
- ),
46
- # gyro
47
- CAMMType.GYRO.value: Float[3],
48
- # acceleration
49
- CAMMType.ACCELERATION.value: Float[3],
50
- # position
51
- CAMMType.POSITION.value: Float[3],
52
- # lat, lon, alt
53
- CAMMType.MIN_GPS.value: Double[3],
54
- CAMMType.GPS.value: C.Struct(
55
- "time_gps_epoch" / Double,
56
- "gps_fix_type" / C.Int32sl,
57
- "latitude" / Double,
58
- "longitude" / Double,
59
- "altitude" / Float,
60
- "horizontal_accuracy" / Float,
61
- "vertical_accuracy" / Float,
62
- "velocity_east" / Float,
63
- "velocity_north" / Float,
64
- "velocity_up" / Float,
65
- "speed_accuracy" / Float,
66
- ),
67
- # magnetic_field
68
- CAMMType.MAGNETIC_FIELD.value: Float[3],
69
- }
70
-
71
- CAMMSampleData = C.Struct(
72
- C.Padding(2),
73
- "type" / C.Int16ul,
74
- "data"
75
- / C.Switch(
76
- C.this.type,
77
- _SWITCH,
78
- ),
79
- )
80
-
81
-
82
- def _parse_point_from_sample(
83
- fp: T.BinaryIO, sample: sample_parser.Sample
84
- ) -> T.Optional[geo.Point]:
85
- fp.seek(sample.offset, io.SEEK_SET)
86
- data = fp.read(sample.size)
87
- box = CAMMSampleData.parse(data)
88
- if box.type == CAMMType.MIN_GPS.value:
89
- return geo.Point(
90
- time=sample.time_offset,
91
- lat=box.data[0],
92
- lon=box.data[1],
93
- alt=box.data[2],
94
- angle=None,
95
- )
96
- elif box.type == CAMMType.GPS.value:
97
- # Not using box.data.time_gps_epoch as the point timestamp
98
- # because it is from another clock
99
- return geo.Point(
100
- time=sample.time_offset,
101
- lat=box.data.latitude,
102
- lon=box.data.longitude,
103
- alt=box.data.altitude,
104
- angle=None,
105
- )
106
- return None
107
-
108
-
109
- def filter_points_by_elst(
110
- points: T.Iterable[geo.Point], elst: T.Sequence[T.Tuple[float, float]]
111
- ) -> T.Generator[geo.Point, None, None]:
112
- empty_elst = [entry for entry in elst if entry[0] == -1]
113
- if empty_elst:
114
- offset = empty_elst[-1][1]
115
- else:
116
- offset = 0
117
-
118
- elst = [entry for entry in elst if entry[0] != -1]
119
-
120
- if not elst:
121
- for p in points:
122
- yield dataclasses.replace(p, time=p.time + offset)
123
- return
124
-
125
- elst.sort(key=lambda entry: entry[0])
126
- elst_idx = 0
127
- for p in points:
128
- if len(elst) <= elst_idx:
129
- break
130
- media_time, duration = elst[elst_idx]
131
- if p.time < media_time:
132
- pass
133
- elif p.time <= media_time + duration:
134
- yield dataclasses.replace(p, time=p.time + offset)
135
- else:
136
- elst_idx += 1
137
-
138
-
139
- def elst_entry_to_seconds(
140
- entry: T.Dict, movie_timescale: int, media_timescale: int
141
- ) -> T.Tuple[float, float]:
142
- assert movie_timescale > 0, "expected positive movie_timescale"
143
- assert media_timescale > 0, "expected positive media_timescale"
144
- media_time, duration = entry["media_time"], entry["segment_duration"]
145
- if media_time != -1:
146
- media_time = media_time / media_timescale
147
- duration = duration / movie_timescale
148
- return (media_time, duration)
149
-
150
-
151
- def _extract_camm_samples(
152
- s: T.BinaryIO,
153
- maxsize: int = -1,
154
- ) -> T.Generator[sample_parser.Sample, None, None]:
155
- samples = sample_parser.parse_samples_from_trak(s, maxsize=maxsize)
156
- camm_samples = (
157
- sample for sample in samples if sample.description["format"] == b"camm"
158
- )
159
- yield from camm_samples
160
-
161
-
162
- def extract_points(fp: T.BinaryIO) -> T.Optional[T.List[geo.Point]]:
163
- """
164
- Return a list of points (could be empty) if it is a valid CAMM video,
165
- otherwise None
166
- """
167
-
168
- points = None
169
- movie_timescale = None
170
- media_timescale = None
171
- elst_entries = None
172
-
173
- for h, s in parser.parse_path(fp, [b"moov", [b"mvhd", b"trak"]]):
174
- if h.type == b"trak":
175
- trak_start_offset = s.tell()
176
-
177
- descriptions = sample_parser.parse_descriptions_from_trak(
178
- s, maxsize=h.maxsize
179
- )
180
- camm_descriptions = [d for d in descriptions if d["format"] == b"camm"]
181
- if camm_descriptions:
182
- s.seek(trak_start_offset, io.SEEK_SET)
183
- camm_samples = _extract_camm_samples(s, h.maxsize)
184
-
185
- points_with_nones = (
186
- _parse_point_from_sample(fp, sample)
187
- for sample in camm_samples
188
- if sample.description["format"] == b"camm"
189
- )
190
-
191
- points = [p for p in points_with_nones if p is not None]
192
- if points:
193
- s.seek(trak_start_offset)
194
- elst_data = parser.parse_box_data_first(
195
- s, [b"edts", b"elst"], maxsize=h.maxsize
196
- )
197
- if elst_data is not None:
198
- elst_entries = cparser.EditBox.parse(elst_data)["entries"]
199
-
200
- s.seek(trak_start_offset)
201
- mdhd_data = parser.parse_box_data_firstx(
202
- s, [b"mdia", b"mdhd"], maxsize=h.maxsize
203
- )
204
- mdhd = cparser.MediaHeaderBox.parse(mdhd_data)
205
- media_timescale = mdhd["timescale"]
206
- else:
207
- assert h.type == b"mvhd"
208
- if not movie_timescale:
209
- mvhd = cparser.MovieHeaderBox.parse(s.read(h.maxsize))
210
- movie_timescale = mvhd["timescale"]
211
-
212
- # exit when both found
213
- if movie_timescale is not None and points:
214
- break
215
-
216
- if points and movie_timescale and media_timescale and elst_entries:
217
- segments = [
218
- elst_entry_to_seconds(entry, movie_timescale, media_timescale)
219
- for entry in elst_entries
220
- ]
221
- points = list(filter_points_by_elst(points, segments))
222
-
223
- return points
224
-
225
-
226
- def parse_gpx(path: pathlib.Path) -> T.List[geo.Point]:
227
- with path.open("rb") as fp:
228
- points = extract_points(fp)
229
- if points is None:
230
- return []
231
- return points
232
-
233
-
234
- MakeOrModel = C.Struct(
235
- "size" / C.Int16ub,
236
- C.Padding(2),
237
- "data" / C.FixedSized(C.this.size, C.GreedyBytes),
238
- )
239
-
240
-
241
- def _decode_quietly(data: bytes, h: parser.Header) -> str:
242
- try:
243
- return data.decode("utf-8")
244
- except UnicodeDecodeError:
245
- LOG.warning("Failed to decode %s: %s", h, data[:512])
246
- return ""
247
-
248
-
249
- def _parse_quietly(data: bytes, h: parser.Header) -> bytes:
250
- try:
251
- parsed = MakeOrModel.parse(data)
252
- except C.ConstructError:
253
- LOG.warning("Failed to parse %s: %s", h, data[:512])
254
- return b""
255
- return parsed["data"]
256
-
257
-
258
- def extract_camera_make_and_model(fp: T.BinaryIO) -> T.Tuple[str, str]:
259
- header_and_stream = parser.parse_path(
260
- fp,
261
- [
262
- b"moov",
263
- b"udta",
264
- [
265
- # Insta360 Titan
266
- b"\xa9mak",
267
- b"\xa9mod",
268
- # RICHO THETA V
269
- b"@mod",
270
- b"@mak",
271
- # RICHO THETA V
272
- b"manu",
273
- b"modl",
274
- ],
275
- ],
276
- )
277
-
278
- make: T.Optional[str] = None
279
- model: T.Optional[str] = None
280
-
281
- try:
282
- for h, s in header_and_stream:
283
- data = s.read(h.maxsize)
284
- if h.type == b"\xa9mak":
285
- make_data = _parse_quietly(data, h)
286
- make_data = make_data.rstrip(b"\x00")
287
- make = _decode_quietly(make_data, h)
288
- elif h.type == b"\xa9mod":
289
- model_data = _parse_quietly(data, h)
290
- model_data = model_data.rstrip(b"\x00")
291
- model = _decode_quietly(model_data, h)
292
- elif h.type in [b"@mak", b"manu"]:
293
- make = _decode_quietly(data, h)
294
- elif h.type in [b"@mod", b"modl"]:
295
- model = _decode_quietly(data, h)
296
- # quit when both found
297
- if make and model:
298
- break
299
- except parser.ParsingError:
300
- pass
301
-
302
- if make:
303
- make = make.strip()
304
- if model:
305
- model = model.strip()
306
- return make or "", model or ""