mapillary-tools 0.12.0__py3-none-any.whl → 0.13.0__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 (57) hide show
  1. mapillary_tools/__init__.py +1 -1
  2. mapillary_tools/api_v4.py +94 -11
  3. mapillary_tools/{geotag → camm}/camm_builder.py +122 -62
  4. mapillary_tools/{geotag → camm}/camm_parser.py +120 -84
  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_videos_from_exiftool_video.py +5 -3
  17. mapillary_tools/geotag/geotag_videos_from_video.py +13 -14
  18. mapillary_tools/geotag/gpmf_gps_filter.py +9 -10
  19. mapillary_tools/geotag/gpmf_parser.py +318 -78
  20. mapillary_tools/mp4/__init__.py +0 -0
  21. mapillary_tools/{geotag → mp4}/construct_mp4_parser.py +32 -16
  22. mapillary_tools/mp4/mp4_sample_parser.py +322 -0
  23. mapillary_tools/{geotag → mp4}/simple_mp4_builder.py +64 -38
  24. mapillary_tools/process_geotag_properties.py +25 -19
  25. mapillary_tools/process_sequence_properties.py +6 -6
  26. mapillary_tools/sample_video.py +17 -16
  27. mapillary_tools/telemetry.py +59 -0
  28. mapillary_tools/types.py +18 -0
  29. mapillary_tools/upload.py +75 -233
  30. mapillary_tools/upload_api_v4.py +8 -16
  31. mapillary_tools/utils.py +9 -16
  32. mapillary_tools/video_data_extraction/cli_options.py +0 -1
  33. mapillary_tools/video_data_extraction/extract_video_data.py +13 -31
  34. mapillary_tools/video_data_extraction/extractors/base_parser.py +13 -11
  35. mapillary_tools/video_data_extraction/extractors/blackvue_parser.py +5 -4
  36. mapillary_tools/video_data_extraction/extractors/camm_parser.py +7 -6
  37. mapillary_tools/video_data_extraction/extractors/exiftool_runtime_parser.py +4 -9
  38. mapillary_tools/video_data_extraction/extractors/exiftool_xml_parser.py +9 -11
  39. mapillary_tools/video_data_extraction/extractors/generic_video_parser.py +6 -11
  40. mapillary_tools/video_data_extraction/extractors/gopro_parser.py +11 -4
  41. mapillary_tools/video_data_extraction/extractors/gpx_parser.py +54 -12
  42. mapillary_tools/video_data_extraction/extractors/nmea_parser.py +3 -3
  43. mapillary_tools/video_data_extraction/video_data_parser_factory.py +13 -20
  44. {mapillary_tools-0.12.0.dist-info → mapillary_tools-0.13.0.dist-info}/METADATA +11 -4
  45. mapillary_tools-0.13.0.dist-info/RECORD +75 -0
  46. {mapillary_tools-0.12.0.dist-info → mapillary_tools-0.13.0.dist-info}/WHEEL +1 -1
  47. mapillary_tools/commands/upload_blackvue.py +0 -33
  48. mapillary_tools/commands/upload_camm.py +0 -33
  49. mapillary_tools/commands/upload_zip.py +0 -33
  50. mapillary_tools/geotag/mp4_sample_parser.py +0 -426
  51. mapillary_tools/process_import_meta_properties.py +0 -76
  52. mapillary_tools-0.12.0.dist-info/RECORD +0 -77
  53. /mapillary_tools/{geotag → mp4}/io_utils.py +0 -0
  54. /mapillary_tools/{geotag → mp4}/simple_mp4_parser.py +0 -0
  55. {mapillary_tools-0.12.0.dist-info → mapillary_tools-0.13.0.dist-info}/LICENSE +0 -0
  56. {mapillary_tools-0.12.0.dist-info → mapillary_tools-0.13.0.dist-info}/entry_points.txt +0 -0
  57. {mapillary_tools-0.12.0.dist-info → mapillary_tools-0.13.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,75 @@
1
+ mapillary_tools/__init__.py,sha256=hZfMXyZ8nUrpqLa-dZCy9tXhu5fH1SZJKEGUd0caUDw,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=WpBGPF_GMPjM_EFqXIutFtpDFL9wj7yEzGNGnfQZUo8,1255
19
+ mapillary_tools/types.py,sha256=6kww2UdKM6YzabYbc862BYzEWtxL2hhxCRFfeDiUtF0,22074
20
+ mapillary_tools/upload.py,sha256=8dQ3ZWsjau1_xZN3ssjGGkBnLKbKIhjC91-zWstYlD8,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=TXZfhu3xGjtrLEWnB14D7aSOrHOoSJef24YSLApiIfY,10631
25
+ mapillary_tools/camm/camm_parser.py,sha256=RaCWeLvS_AyHD6B6wDUu9DAsdfByVHMAPTqEqjtFibE,9734
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=zEbC0kVf_iw9ioIyJLL-gYN_QvAOEdAoEczCBkizl38,5122
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=yAXzFAlj4VsbFju-kNDBxhT1K6kfmZL37lrM26Xkg0M,22514
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=sr8oDbC9lGyHHp8qkpQBmWOf4U_umlokaJM0kPVKgzw,1314
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=FRysFhN2aa0MGnzLO7pcZkwo8790GUelIZg9pddZvic,2594
69
+ mapillary_tools/video_data_extraction/extractors/nmea_parser.py,sha256=raSXavBvP-0LJCB_TwLL0mOv2uHSsB744igTsaKAaGc,658
70
+ mapillary_tools-0.13.0.dist-info/LICENSE,sha256=l2D8cKfFmmJq_wcVq_JElPJrlvWQOzNWx7gMLINucxc,1292
71
+ mapillary_tools-0.13.0.dist-info/METADATA,sha256=IjaJljFtCVqGSVhabQ2MptJlan76aUoGLulB1ecs9-E,19758
72
+ mapillary_tools-0.13.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
73
+ mapillary_tools-0.13.0.dist-info/entry_points.txt,sha256=A3f3LP-BO_P-U8Y29QfpT4jx6Mjk3sXjTi2Yew4bvj8,75
74
+ mapillary_tools-0.13.0.dist-info/top_level.txt,sha256=FbDkMgOrt1S70ho1WSBrOwzKOSkJFDwwqFOoY5-527s,16
75
+ mapillary_tools-0.13.0.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,426 +0,0 @@
1
- import datetime
2
- import io
3
- import typing as T
4
- from pathlib import Path
5
-
6
- from . import construct_mp4_parser as cparser, simple_mp4_parser as parser
7
-
8
-
9
- class RawSample(T.NamedTuple):
10
- # 1-based index
11
- description_idx: int
12
- # sample offset
13
- offset: int
14
- # sample size
15
- size: int
16
- # sample_delta read from stts entries,
17
- # i.e. STTS(n) in the forumula DT(n+1) = DT(n) + STTS(n)
18
- timedelta: int
19
- # sample composition offset,
20
- # i.e. CTTS(n) in the forumula CT(n) = DT(n) + CTTS(n).
21
- composition_offset: int
22
- # if it is a sync sample
23
- is_sync: bool
24
-
25
-
26
- # TODO: can not inherit RawSample?
27
- class Sample(T.NamedTuple):
28
- # copied from RawSample
29
-
30
- # 1-based index
31
- description_idx: int
32
- # sample offset
33
- offset: int
34
- # sample size
35
- size: int
36
- # sample delta in seconds read from stts entries,
37
- # i.e. (STTS(n) / timescale) in the forumula DT(n+1) = DT(n) + STTS(n)
38
- timedelta: float
39
- # sample composition offset in seconds,
40
- # i.e. (CTTS(n) / timescale) in the forumula CT(n) = DT(n) + CTTS(n).
41
- composition_offset: float
42
- # if it is a sync sample
43
- is_sync: bool
44
-
45
- # extended fields below
46
-
47
- # accumulated sample_delta in seconds,
48
- # i.e. (DT(n) / timescale) in the forumula DT(n+1) = DT(n) + STTS(n)
49
- time_offset: T.Union[int, float]
50
- # accumulated composition offset in seconds,
51
- # i.e. (CT(n) / timescale) in the forumula CT(n) = DT(n) + CTTS(n).
52
- composition_time_offset: T.Union[int, float]
53
- # reference to the sample description
54
- description: T.Dict
55
-
56
-
57
- def _extract_raw_samples(
58
- sizes: T.Sequence[int],
59
- chunk_entries: T.Sequence[T.Dict],
60
- chunk_offsets: T.Sequence[int],
61
- timedeltas: T.Sequence[int],
62
- composition_offsets: T.Optional[T.Sequence[int]],
63
- syncs: T.Optional[T.Set[int]],
64
- ) -> T.Generator[RawSample, None, None]:
65
- if not sizes:
66
- return
67
-
68
- if not chunk_entries:
69
- return
70
-
71
- assert len(sizes) <= len(
72
- timedeltas
73
- ), f"got less ({len(timedeltas)}) sample time deltas (stts) than expected ({len(sizes)})"
74
-
75
- sample_idx = 0
76
- chunk_idx = 0
77
-
78
- # iterate compressed chunks
79
- for entry_idx, entry in enumerate(chunk_entries):
80
- if entry_idx + 1 < len(chunk_entries):
81
- nbr_chunks = (
82
- chunk_entries[entry_idx + 1]["first_chunk"] - entry["first_chunk"]
83
- )
84
- else:
85
- nbr_chunks = 1
86
-
87
- # iterate chunks
88
- for _ in range(nbr_chunks):
89
- sample_offset = chunk_offsets[chunk_idx]
90
- # iterate samples in this chunk
91
- for _ in range(entry["samples_per_chunk"]):
92
- is_sync = syncs is None or (sample_idx + 1) in syncs
93
- composition_offset = (
94
- composition_offsets[sample_idx]
95
- if composition_offsets is not None
96
- else 0
97
- )
98
- yield RawSample(
99
- description_idx=entry["sample_description_index"],
100
- offset=sample_offset,
101
- size=sizes[sample_idx],
102
- timedelta=timedeltas[sample_idx],
103
- composition_offset=composition_offset,
104
- is_sync=is_sync,
105
- )
106
- sample_offset += sizes[sample_idx]
107
- sample_idx += 1
108
- chunk_idx += 1
109
-
110
- # below handles the single-entry case:
111
- # If all the chunks have the same number of samples per chunk
112
- # and use the same sample description, this table has one entry.
113
-
114
- # iterate chunks
115
- while sample_idx < len(sizes):
116
- sample_offset = chunk_offsets[chunk_idx]
117
- # iterate samples in this chunk
118
- for _ in range(chunk_entries[-1]["samples_per_chunk"]):
119
- is_sync = syncs is None or (sample_idx + 1) in syncs
120
- composition_offset = (
121
- composition_offsets[sample_idx]
122
- if composition_offsets is not None
123
- else 0
124
- )
125
- yield RawSample(
126
- description_idx=chunk_entries[-1]["sample_description_index"],
127
- offset=sample_offset,
128
- size=sizes[sample_idx],
129
- timedelta=timedeltas[sample_idx],
130
- composition_offset=composition_offset,
131
- is_sync=is_sync,
132
- )
133
- sample_offset += sizes[sample_idx]
134
- sample_idx += 1
135
- chunk_idx += 1
136
-
137
-
138
- def _extract_samples(
139
- raw_samples: T.Iterator[RawSample],
140
- descriptions: T.List,
141
- ) -> T.Generator[Sample, None, None]:
142
- acc_delta = 0
143
- for raw_sample in raw_samples:
144
- yield Sample(
145
- description_idx=raw_sample.description_idx,
146
- offset=raw_sample.offset,
147
- size=raw_sample.size,
148
- timedelta=raw_sample.timedelta,
149
- composition_offset=raw_sample.composition_offset,
150
- is_sync=raw_sample.is_sync,
151
- description=descriptions[raw_sample.description_idx - 1],
152
- time_offset=acc_delta,
153
- # CT(n) = DT(n) + CTTS(n)
154
- composition_time_offset=(acc_delta + raw_sample.composition_offset),
155
- )
156
- acc_delta += raw_sample.timedelta
157
-
158
-
159
- def _apply_timescale(sample: Sample, media_timescale: int) -> Sample:
160
- return Sample(
161
- description_idx=sample.description_idx,
162
- offset=sample.offset,
163
- size=sample.size,
164
- timedelta=sample.timedelta / media_timescale,
165
- composition_offset=sample.composition_offset / media_timescale,
166
- is_sync=sample.is_sync,
167
- description=sample.description,
168
- time_offset=sample.time_offset / media_timescale,
169
- composition_time_offset=sample.composition_time_offset / media_timescale,
170
- )
171
-
172
-
173
- def parse_raw_samples_from_stbl(
174
- stbl: T.BinaryIO,
175
- maxsize: int = -1,
176
- ) -> T.Tuple[T.List[T.Dict], T.Generator[RawSample, None, None]]:
177
- """
178
- DEPRECATED: use parse_raw_samples_from_stbl_bytes instead
179
- """
180
-
181
- descriptions = []
182
- sizes = []
183
- chunk_offsets = []
184
- chunk_entries = []
185
- timedeltas: T.List[int] = []
186
- composition_offsets: T.Optional[T.List[int]] = None
187
- syncs: T.Optional[T.Set[int]] = None
188
-
189
- for h, s in parser.parse_boxes(stbl, maxsize=maxsize, extend_eof=False):
190
- if h.type == b"stsd":
191
- box = cparser.SampleDescriptionBox.parse(s.read(h.maxsize))
192
- descriptions = list(box.entries)
193
- elif h.type == b"stsz":
194
- box = cparser.SampleSizeBox.parse(s.read(h.maxsize))
195
- if box.sample_size == 0:
196
- sizes = list(box.entries)
197
- else:
198
- sizes = [box.sample_size for _ in range(box.sample_count)]
199
- elif h.type == b"stco":
200
- box = cparser.ChunkOffsetBox.parse(s.read(h.maxsize))
201
- chunk_offsets = list(box.entries)
202
- elif h.type == b"co64":
203
- box = cparser.ChunkLargeOffsetBox.parse(s.read(h.maxsize))
204
- chunk_offsets = list(box.entries)
205
- elif h.type == b"stsc":
206
- box = cparser.SampleToChunkBox.parse(s.read(h.maxsize))
207
- chunk_entries = list(box.entries)
208
- elif h.type == b"stts":
209
- timedeltas = []
210
- box = cparser.TimeToSampleBox.parse(s.read(h.maxsize))
211
- for entry in box.entries:
212
- for _ in range(entry.sample_count):
213
- timedeltas.append(entry.sample_delta)
214
- elif h.type == b"ctts":
215
- composition_offsets = []
216
- box = cparser.CompositionTimeToSampleBox.parse(s.read(h.maxsize))
217
- for entry in box.entries:
218
- for _ in range(entry.sample_count):
219
- composition_offsets.append(entry.sample_offset)
220
- elif h.type == b"stss":
221
- box = cparser.SyncSampleBox.parse(s.read(h.maxsize))
222
- syncs = set(box.entries)
223
-
224
- # some stbl have less timedeltas than the sample count i.e. len(sizes),
225
- # in this case append 0's to timedeltas
226
- while len(timedeltas) < len(sizes):
227
- timedeltas.append(0)
228
- if composition_offsets is not None:
229
- while len(composition_offsets) < len(sizes):
230
- composition_offsets.append(0)
231
-
232
- raw_samples = _extract_raw_samples(
233
- sizes, chunk_entries, chunk_offsets, timedeltas, composition_offsets, syncs
234
- )
235
- return descriptions, raw_samples
236
-
237
-
238
- STBLBoxlistConstruct = cparser.Box64ConstructBuilder(
239
- T.cast(cparser.SwitchMapType, cparser.CMAP[b"stbl"])
240
- ).BoxList
241
-
242
-
243
- def parse_raw_samples_from_stbl_bytes(
244
- stbl: bytes,
245
- ) -> T.Tuple[T.List[T.Dict], T.Generator[RawSample, None, None]]:
246
- descriptions = []
247
- sizes = []
248
- chunk_offsets = []
249
- chunk_entries = []
250
- timedeltas: T.List[int] = []
251
- composition_offsets: T.Optional[T.List[int]] = None
252
- syncs: T.Optional[T.Set[int]] = None
253
-
254
- stbl_boxes = T.cast(T.Sequence[cparser.BoxDict], STBLBoxlistConstruct.parse(stbl))
255
-
256
- for box in stbl_boxes:
257
- data: T.Dict = T.cast(T.Dict, box["data"])
258
-
259
- if box["type"] == b"stsd":
260
- descriptions = list(data["entries"])
261
- elif box["type"] == b"stsz":
262
- if data["sample_size"] == 0:
263
- sizes = list(data["entries"])
264
- else:
265
- sizes = [data["sample_size"] for _ in range(data["sample_count"])]
266
- elif box["type"] == b"stco":
267
- chunk_offsets = list(data["entries"])
268
- elif box["type"] == b"co64":
269
- chunk_offsets = list(data["entries"])
270
- elif box["type"] == b"stsc":
271
- chunk_entries = list(data["entries"])
272
- elif box["type"] == b"stts":
273
- timedeltas = []
274
- for entry in data["entries"]:
275
- for _ in range(entry["sample_count"]):
276
- timedeltas.append(entry["sample_delta"])
277
- elif box["type"] == b"ctts":
278
- composition_offsets = []
279
- for entry in data["entries"]:
280
- for _ in range(entry["sample_count"]):
281
- composition_offsets.append(entry["sample_offset"])
282
- elif box["type"] == b"stss":
283
- syncs = set(data["entries"])
284
-
285
- # some stbl have less timedeltas than the sample count i.e. len(sizes),
286
- # in this case append 0's to timedeltas
287
- while len(timedeltas) < len(sizes):
288
- timedeltas.append(0)
289
- if composition_offsets is not None:
290
- while len(composition_offsets) < len(sizes):
291
- composition_offsets.append(0)
292
-
293
- raw_samples = _extract_raw_samples(
294
- sizes, chunk_entries, chunk_offsets, timedeltas, composition_offsets, syncs
295
- )
296
- return descriptions, raw_samples
297
-
298
-
299
- def parse_descriptions_from_trak(trak: T.BinaryIO, maxsize: int = -1) -> T.List[T.Dict]:
300
- data = parser.parse_box_data_first(
301
- trak, [b"mdia", b"minf", b"stbl", b"stsd"], maxsize=maxsize
302
- )
303
- if data is None:
304
- return []
305
- box = cparser.SampleDescriptionBox.parse(data)
306
- return list(box.entries)
307
-
308
-
309
- def parse_samples_from_trak(
310
- trak: T.BinaryIO,
311
- maxsize: int = -1,
312
- ) -> T.Generator[Sample, None, None]:
313
- trak_start_offset = trak.tell()
314
-
315
- trak.seek(trak_start_offset, io.SEEK_SET)
316
- mdhd_box = parser.parse_box_data_firstx(trak, [b"mdia", b"mdhd"], maxsize=maxsize)
317
- mdhd = T.cast(T.Dict, cparser.MediaHeaderBox.parse(mdhd_box))
318
-
319
- trak.seek(trak_start_offset, io.SEEK_SET)
320
- h, s = parser.parse_box_path_firstx(
321
- trak, [b"mdia", b"minf", b"stbl"], maxsize=maxsize
322
- )
323
- descriptions, raw_samples = parse_raw_samples_from_stbl(s, maxsize=h.maxsize)
324
-
325
- yield from (
326
- _apply_timescale(s, mdhd["timescale"])
327
- for s in _extract_samples(raw_samples, descriptions)
328
- )
329
-
330
-
331
- STSDBoxListConstruct = cparser.Box64ConstructBuilder(
332
- # pyre-ignore[6]: pyre does not support recursive type SwitchMapType
333
- {b"stsd": cparser.CMAP[b"stsd"]}
334
- ).BoxList
335
-
336
-
337
- class TrackBoxParser:
338
- trak_boxes: T.Sequence[cparser.BoxDict]
339
- stbl_data: bytes
340
-
341
- def __init__(self, trak_boxes: T.Sequence[cparser.BoxDict]):
342
- self.trak_boxes = trak_boxes
343
- stbl = cparser.find_box_at_pathx(self.trak_boxes, [b"mdia", b"minf", b"stbl"])
344
- self.stbl_data = T.cast(bytes, stbl["data"])
345
-
346
- def tkhd(self) -> T.Dict:
347
- return T.cast(
348
- T.Dict, cparser.find_box_at_pathx(self.trak_boxes, [b"tkhd"])["data"]
349
- )
350
-
351
- def is_video_track(self) -> bool:
352
- hdlr = cparser.find_box_at_pathx(self.trak_boxes, [b"mdia", b"hdlr"])
353
- return T.cast(T.Dict[str, T.Any], hdlr["data"])["handler_type"] == b"vide"
354
-
355
- def parse_sample_description(self) -> T.Dict:
356
- boxes = STSDBoxListConstruct.parse(self.stbl_data)
357
- stsd = cparser.find_box_at_pathx(
358
- T.cast(T.Sequence[cparser.BoxDict], boxes), [b"stsd"]
359
- )
360
- return T.cast(T.Dict, stsd["data"])
361
-
362
- def parse_raw_samples(self) -> T.Generator[RawSample, None, None]:
363
- _, raw_samples = parse_raw_samples_from_stbl_bytes(self.stbl_data)
364
- yield from raw_samples
365
-
366
- def parse_samples(self) -> T.Generator[Sample, None, None]:
367
- descriptions, raw_samples = parse_raw_samples_from_stbl_bytes(self.stbl_data)
368
- mdhd = T.cast(
369
- T.Dict,
370
- cparser.find_box_at_pathx(self.trak_boxes, [b"mdia", b"mdhd"])["data"],
371
- )
372
- yield from (
373
- _apply_timescale(s, mdhd["timescale"])
374
- for s in _extract_samples(raw_samples, descriptions)
375
- )
376
-
377
-
378
- class MovieBoxParser:
379
- moov_boxes: T.Sequence[cparser.BoxDict]
380
-
381
- def __init__(self, moov: bytes):
382
- self.moov_boxes = T.cast(
383
- T.Sequence[cparser.BoxDict],
384
- cparser.MOOVWithoutSTBLBuilderConstruct.BoxList.parse(moov),
385
- )
386
-
387
- @classmethod
388
- def parse_file(cls, video_path: Path) -> "MovieBoxParser":
389
- with video_path.open("rb") as fp:
390
- moov = parser.parse_box_data_firstx(fp, [b"moov"])
391
- return MovieBoxParser(moov)
392
-
393
- def mvhd(self):
394
- mvhd = cparser.find_box_at_pathx(self.moov_boxes, [b"mvhd"])
395
- return mvhd["data"]
396
-
397
- def parse_tracks(self) -> T.Generator[TrackBoxParser, None, None]:
398
- for box in self.moov_boxes:
399
- if box["type"] == b"trak":
400
- yield TrackBoxParser(T.cast(T.Sequence[cparser.BoxDict], box["data"]))
401
-
402
- def parse_track_at(self, stream_idx: int) -> TrackBoxParser:
403
- """
404
- stream_idx should be the stream_index specifier. See http://ffmpeg.org/ffmpeg.html#Stream-specifiers-1
405
- > Stream numbering is based on the order of the streams as detected by libavformat
406
- """
407
- trak_boxes = [box for box in self.moov_boxes if box["type"] == b"trak"]
408
- if not (0 <= stream_idx < len(trak_boxes)):
409
- raise IndexError(
410
- "unable to read stream at %d from the track list (length %d)",
411
- stream_idx,
412
- len(trak_boxes),
413
- )
414
- return TrackBoxParser(
415
- T.cast(T.Sequence[cparser.BoxDict], trak_boxes[stream_idx]["data"])
416
- )
417
-
418
-
419
- _DT_1904 = datetime.datetime.utcfromtimestamp(0).replace(year=1904)
420
-
421
-
422
- def to_datetime(seconds_since_1904: int) -> datetime.datetime:
423
- """
424
- Convert seconds since midnight, Jan. 1, 1904, in UTC time
425
- """
426
- return _DT_1904 + datetime.timedelta(seconds=seconds_since_1904)
@@ -1,76 +0,0 @@
1
- import logging
2
- import typing as T
3
-
4
- from . import types
5
-
6
-
7
- LOG = logging.getLogger(__name__)
8
-
9
-
10
- def format_orientation(orientation: int) -> int:
11
- """
12
- Convert orientation from clockwise degrees to exif tag
13
-
14
- # see http://sylvana.net/jpegcrop/exif_orientation.html
15
- """
16
- mapping: T.Mapping[int, int] = {
17
- 0: 1,
18
- 90: 8,
19
- 180: 3,
20
- 270: 6,
21
- }
22
- if orientation not in mapping:
23
- raise ValueError("Orientation value has to be 0, 90, 180, or 270")
24
-
25
- return mapping[orientation]
26
-
27
-
28
- def process_import_meta_properties(
29
- metadatas: T.List[types.MetadataOrError],
30
- orientation=None,
31
- device_make=None,
32
- device_model=None,
33
- GPS_accuracy=None,
34
- add_file_name=False,
35
- add_import_date=False,
36
- custom_meta_data=None,
37
- camera_uuid=None,
38
- ) -> T.List[types.MetadataOrError]:
39
- if add_file_name:
40
- LOG.warning("The option --add_file_name is not needed any more since v0.10.0")
41
-
42
- if add_import_date:
43
- LOG.warning("The option --add_import_date is not needed any more since v0.10.0")
44
-
45
- if custom_meta_data:
46
- LOG.warning(
47
- "The option --custom_meta_data is not needed any more since v0.10.0"
48
- )
49
-
50
- for metadata in metadatas:
51
- if isinstance(metadata, types.ErrorMetadata):
52
- continue
53
-
54
- if device_make is not None:
55
- if isinstance(metadata, types.VideoMetadata):
56
- metadata.make = device_make
57
- else:
58
- metadata.MAPDeviceMake = device_make
59
-
60
- if device_model is not None:
61
- if isinstance(metadata, types.VideoMetadata):
62
- metadata.model = device_model
63
- elif isinstance(metadata, types.ImageMetadata):
64
- metadata.MAPDeviceModel = device_model
65
-
66
- if isinstance(metadata, types.ImageMetadata):
67
- if orientation is not None:
68
- metadata.MAPOrientation = format_orientation(orientation)
69
-
70
- if GPS_accuracy is not None:
71
- metadata.MAPGPSAccuracyMeters = float(GPS_accuracy)
72
-
73
- if camera_uuid is not None:
74
- metadata.MAPCameraUUID = camera_uuid
75
-
76
- return metadatas