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.
- mapillary_tools/__init__.py +1 -1
- mapillary_tools/api_v4.py +94 -4
- mapillary_tools/{geotag → camm}/camm_builder.py +73 -61
- mapillary_tools/camm/camm_parser.py +561 -0
- mapillary_tools/commands/__init__.py +0 -1
- mapillary_tools/commands/__main__.py +0 -6
- mapillary_tools/commands/process.py +0 -50
- mapillary_tools/commands/upload.py +1 -26
- mapillary_tools/constants.py +2 -2
- mapillary_tools/exiftool_read_video.py +13 -11
- mapillary_tools/ffmpeg.py +2 -2
- mapillary_tools/geo.py +0 -54
- mapillary_tools/geotag/blackvue_parser.py +4 -4
- mapillary_tools/geotag/geotag_images_from_exif.py +2 -1
- mapillary_tools/geotag/geotag_images_from_exiftool_both_image_and_video.py +0 -1
- mapillary_tools/geotag/geotag_images_from_gpx_file.py +7 -1
- mapillary_tools/geotag/geotag_videos_from_exiftool_video.py +5 -3
- mapillary_tools/geotag/geotag_videos_from_video.py +13 -14
- mapillary_tools/geotag/gpmf_gps_filter.py +9 -10
- mapillary_tools/geotag/gpmf_parser.py +346 -83
- mapillary_tools/mp4/__init__.py +0 -0
- mapillary_tools/{geotag → mp4}/construct_mp4_parser.py +32 -16
- mapillary_tools/mp4/mp4_sample_parser.py +322 -0
- mapillary_tools/{geotag → mp4}/simple_mp4_builder.py +64 -38
- mapillary_tools/process_geotag_properties.py +25 -19
- mapillary_tools/process_sequence_properties.py +6 -6
- mapillary_tools/sample_video.py +17 -16
- mapillary_tools/telemetry.py +71 -0
- mapillary_tools/types.py +18 -0
- mapillary_tools/upload.py +74 -233
- mapillary_tools/upload_api_v4.py +8 -9
- mapillary_tools/utils.py +9 -16
- mapillary_tools/video_data_extraction/cli_options.py +0 -1
- mapillary_tools/video_data_extraction/extract_video_data.py +13 -31
- mapillary_tools/video_data_extraction/extractors/base_parser.py +13 -11
- mapillary_tools/video_data_extraction/extractors/blackvue_parser.py +5 -4
- mapillary_tools/video_data_extraction/extractors/camm_parser.py +13 -16
- mapillary_tools/video_data_extraction/extractors/exiftool_runtime_parser.py +4 -9
- mapillary_tools/video_data_extraction/extractors/exiftool_xml_parser.py +9 -11
- mapillary_tools/video_data_extraction/extractors/generic_video_parser.py +6 -11
- mapillary_tools/video_data_extraction/extractors/gopro_parser.py +11 -4
- mapillary_tools/video_data_extraction/extractors/gpx_parser.py +90 -11
- mapillary_tools/video_data_extraction/extractors/nmea_parser.py +3 -3
- mapillary_tools/video_data_extraction/video_data_parser_factory.py +13 -20
- {mapillary_tools-0.12.1.dist-info → mapillary_tools-0.13.1.dist-info}/METADATA +10 -3
- mapillary_tools-0.13.1.dist-info/RECORD +75 -0
- {mapillary_tools-0.12.1.dist-info → mapillary_tools-0.13.1.dist-info}/WHEEL +1 -1
- mapillary_tools/commands/upload_blackvue.py +0 -33
- mapillary_tools/commands/upload_camm.py +0 -33
- mapillary_tools/commands/upload_zip.py +0 -33
- mapillary_tools/geotag/camm_parser.py +0 -306
- mapillary_tools/geotag/mp4_sample_parser.py +0 -426
- mapillary_tools/process_import_meta_properties.py +0 -76
- mapillary_tools-0.12.1.dist-info/RECORD +0 -77
- /mapillary_tools/{geotag → mp4}/io_utils.py +0 -0
- /mapillary_tools/{geotag → mp4}/simple_mp4_parser.py +0 -0
- {mapillary_tools-0.12.1.dist-info → mapillary_tools-0.13.1.dist-info}/LICENSE +0 -0
- {mapillary_tools-0.12.1.dist-info → mapillary_tools-0.13.1.dist-info}/entry_points.txt +0 -0
- {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,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 ""
|