mapillary-tools 0.12.1__tar.gz → 0.13.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mapillary_tools-0.12.1/mapillary_tools.egg-info → mapillary_tools-0.13.1}/PKG-INFO +10 -3
- mapillary_tools-0.13.1/mapillary_tools/__init__.py +1 -0
- mapillary_tools-0.13.1/mapillary_tools/api_v4.py +151 -0
- {mapillary_tools-0.12.1/mapillary_tools/geotag → mapillary_tools-0.13.1/mapillary_tools/camm}/camm_builder.py +73 -61
- mapillary_tools-0.13.1/mapillary_tools/camm/camm_parser.py +561 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/commands/__init__.py +0 -1
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/commands/__main__.py +0 -6
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/commands/process.py +0 -50
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/commands/upload.py +1 -26
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/constants.py +2 -2
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/exiftool_read_video.py +13 -11
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/ffmpeg.py +2 -2
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/geo.py +0 -54
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/geotag/blackvue_parser.py +4 -4
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/geotag/geotag_images_from_exif.py +2 -1
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/geotag/geotag_images_from_exiftool_both_image_and_video.py +0 -1
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/geotag/geotag_images_from_gpx_file.py +7 -1
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/geotag/geotag_videos_from_exiftool_video.py +5 -3
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/geotag/geotag_videos_from_video.py +13 -14
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/geotag/gpmf_gps_filter.py +9 -10
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/geotag/gpmf_parser.py +346 -83
- mapillary_tools-0.13.1/mapillary_tools/mp4/__init__.py +0 -0
- {mapillary_tools-0.12.1/mapillary_tools/geotag → mapillary_tools-0.13.1/mapillary_tools/mp4}/construct_mp4_parser.py +32 -16
- mapillary_tools-0.13.1/mapillary_tools/mp4/mp4_sample_parser.py +322 -0
- {mapillary_tools-0.12.1/mapillary_tools/geotag → mapillary_tools-0.13.1/mapillary_tools/mp4}/simple_mp4_builder.py +64 -38
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/process_geotag_properties.py +25 -19
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/process_sequence_properties.py +6 -6
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/sample_video.py +17 -16
- mapillary_tools-0.13.1/mapillary_tools/telemetry.py +71 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/types.py +18 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/upload.py +74 -233
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/upload_api_v4.py +8 -9
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/utils.py +9 -16
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/video_data_extraction/cli_options.py +0 -1
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/video_data_extraction/extract_video_data.py +13 -31
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/video_data_extraction/extractors/base_parser.py +13 -11
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/video_data_extraction/extractors/blackvue_parser.py +5 -4
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/video_data_extraction/extractors/camm_parser.py +13 -16
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/video_data_extraction/extractors/exiftool_runtime_parser.py +4 -9
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/video_data_extraction/extractors/exiftool_xml_parser.py +9 -11
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/video_data_extraction/extractors/generic_video_parser.py +6 -11
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/video_data_extraction/extractors/gopro_parser.py +11 -4
- mapillary_tools-0.13.1/mapillary_tools/video_data_extraction/extractors/gpx_parser.py +108 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/video_data_extraction/extractors/nmea_parser.py +3 -3
- mapillary_tools-0.13.1/mapillary_tools/video_data_extraction/video_data_parser_factory.py +39 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1/mapillary_tools.egg-info}/PKG-INFO +10 -3
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools.egg-info/SOURCES.txt +9 -11
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools.egg-info/requires.txt +0 -3
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/requirements.txt +0 -1
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/schema/image_description_schema.json +14 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/setup.py +2 -0
- mapillary_tools-0.12.1/mapillary_tools/__init__.py +0 -1
- mapillary_tools-0.12.1/mapillary_tools/api_v4.py +0 -61
- mapillary_tools-0.12.1/mapillary_tools/commands/upload_blackvue.py +0 -33
- mapillary_tools-0.12.1/mapillary_tools/commands/upload_camm.py +0 -33
- mapillary_tools-0.12.1/mapillary_tools/commands/upload_zip.py +0 -33
- mapillary_tools-0.12.1/mapillary_tools/geotag/camm_parser.py +0 -306
- mapillary_tools-0.12.1/mapillary_tools/geotag/mp4_sample_parser.py +0 -426
- mapillary_tools-0.12.1/mapillary_tools/process_import_meta_properties.py +0 -76
- mapillary_tools-0.12.1/mapillary_tools/video_data_extraction/extractors/gpx_parser.py +0 -29
- mapillary_tools-0.12.1/mapillary_tools/video_data_extraction/video_data_parser_factory.py +0 -46
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/LICENSE +0 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/MANIFEST.in +0 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/README.md +0 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/authenticate.py +0 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/commands/authenticate.py +0 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/commands/process_and_upload.py +0 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/commands/sample_video.py +0 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/commands/video_process.py +0 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/commands/video_process_and_upload.py +0 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/commands/zip.py +0 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/config.py +0 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/exceptions.py +0 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/exif_read.py +0 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/exif_write.py +0 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/exiftool_read.py +0 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/geotag/__init__.py +0 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/geotag/geotag_from_generic.py +0 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/geotag/geotag_images_from_exiftool.py +0 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/geotag/geotag_images_from_gpx.py +0 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/geotag/geotag_images_from_nmea_file.py +0 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/geotag/geotag_images_from_video.py +0 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/geotag/gps_filter.py +0 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/geotag/utils.py +0 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/history.py +0 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/ipc.py +0 -0
- {mapillary_tools-0.12.1/mapillary_tools/geotag → mapillary_tools-0.13.1/mapillary_tools/mp4}/io_utils.py +0 -0
- {mapillary_tools-0.12.1/mapillary_tools/geotag → mapillary_tools-0.13.1/mapillary_tools/mp4}/simple_mp4_parser.py +0 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools/uploader.py +0 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools.egg-info/dependency_links.txt +0 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools.egg-info/entry_points.txt +0 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/mapillary_tools.egg-info/top_level.txt +0 -0
- {mapillary_tools-0.12.1 → mapillary_tools-0.13.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: mapillary_tools
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.1
|
|
4
4
|
Summary: Mapillary Image/Video Import Pipeline
|
|
5
5
|
Home-page: https://github.com/mapillary/mapillary_tools
|
|
6
6
|
Author: Mapillary
|
|
@@ -18,7 +18,14 @@ Requires-Dist: requests[socks]<3.0.0,>=2.20.0
|
|
|
18
18
|
Requires-Dist: tqdm<5.0,>=4.0
|
|
19
19
|
Requires-Dist: typing_extensions
|
|
20
20
|
Requires-Dist: jsonschema~=4.17.3
|
|
21
|
-
|
|
21
|
+
Dynamic: author
|
|
22
|
+
Dynamic: description
|
|
23
|
+
Dynamic: description-content-type
|
|
24
|
+
Dynamic: home-page
|
|
25
|
+
Dynamic: license
|
|
26
|
+
Dynamic: requires-dist
|
|
27
|
+
Dynamic: requires-python
|
|
28
|
+
Dynamic: summary
|
|
22
29
|
|
|
23
30
|
<p align="center">
|
|
24
31
|
<a href="https://github.com/mapillary/mapillary_tools/">
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VERSION = "0.13.1"
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import ssl
|
|
4
|
+
import typing as T
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
from requests.adapters import HTTPAdapter
|
|
8
|
+
|
|
9
|
+
LOG = logging.getLogger(__name__)
|
|
10
|
+
MAPILLARY_CLIENT_TOKEN = os.getenv(
|
|
11
|
+
"MAPILLARY_CLIENT_TOKEN", "MLY|5675152195860640|6b02c72e6e3c801e5603ab0495623282"
|
|
12
|
+
)
|
|
13
|
+
MAPILLARY_GRAPH_API_ENDPOINT = os.getenv(
|
|
14
|
+
"MAPILLARY_GRAPH_API_ENDPOINT", "https://graph.mapillary.com"
|
|
15
|
+
)
|
|
16
|
+
REQUESTS_TIMEOUT = 60 # 1 minutes
|
|
17
|
+
USE_SYSTEM_CERTS: bool = False
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class HTTPSystemCertsAdapter(HTTPAdapter):
|
|
21
|
+
"""
|
|
22
|
+
This adapter uses the system's certificate store instead of the certifi module.
|
|
23
|
+
|
|
24
|
+
The implementation is based on the project https://pypi.org/project/pip-system-certs/,
|
|
25
|
+
which has a system-wide effect.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def init_poolmanager(self, *args, **kwargs):
|
|
29
|
+
ssl_context = ssl.create_default_context()
|
|
30
|
+
ssl_context.load_default_certs()
|
|
31
|
+
kwargs["ssl_context"] = ssl_context
|
|
32
|
+
|
|
33
|
+
super().init_poolmanager(*args, **kwargs)
|
|
34
|
+
|
|
35
|
+
def cert_verify(self, *args, **kwargs):
|
|
36
|
+
super().cert_verify(*args, **kwargs)
|
|
37
|
+
|
|
38
|
+
# By default Python requests uses the ca_certs from the certifi module
|
|
39
|
+
# But we want to use the certificate store instead.
|
|
40
|
+
# By clearing the ca_certs variable we force it to fall back on that behaviour (handled in urllib3)
|
|
41
|
+
if "conn" in kwargs:
|
|
42
|
+
conn = kwargs["conn"]
|
|
43
|
+
else:
|
|
44
|
+
conn = args[0]
|
|
45
|
+
|
|
46
|
+
conn.ca_certs = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def request_post(
|
|
50
|
+
url: str,
|
|
51
|
+
data: T.Optional[T.Any] = None,
|
|
52
|
+
json: T.Optional[dict] = None,
|
|
53
|
+
**kwargs,
|
|
54
|
+
) -> requests.Response:
|
|
55
|
+
global USE_SYSTEM_CERTS
|
|
56
|
+
|
|
57
|
+
if USE_SYSTEM_CERTS:
|
|
58
|
+
with requests.Session() as session:
|
|
59
|
+
session.mount("https://", HTTPSystemCertsAdapter())
|
|
60
|
+
return session.post(url, data=data, json=json, **kwargs)
|
|
61
|
+
|
|
62
|
+
else:
|
|
63
|
+
try:
|
|
64
|
+
return requests.post(url, data=data, json=json, **kwargs)
|
|
65
|
+
except requests.exceptions.SSLError as ex:
|
|
66
|
+
if "SSLCertVerificationError" not in str(ex):
|
|
67
|
+
raise ex
|
|
68
|
+
USE_SYSTEM_CERTS = True
|
|
69
|
+
# HTTPSConnectionPool(host='graph.mapillary.com', port=443): Max retries exceeded with url: /login (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1018)')))
|
|
70
|
+
LOG.warning(
|
|
71
|
+
"SSL error occurred, falling back to system SSL certificates: %s", ex
|
|
72
|
+
)
|
|
73
|
+
with requests.Session() as session:
|
|
74
|
+
session.mount("https://", HTTPSystemCertsAdapter())
|
|
75
|
+
return session.post(url, data=data, json=json, **kwargs)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def request_get(
|
|
79
|
+
url: str,
|
|
80
|
+
params: T.Optional[dict] = None,
|
|
81
|
+
**kwargs,
|
|
82
|
+
) -> requests.Response:
|
|
83
|
+
global USE_SYSTEM_CERTS
|
|
84
|
+
|
|
85
|
+
if USE_SYSTEM_CERTS:
|
|
86
|
+
with requests.Session() as session:
|
|
87
|
+
session.mount("https://", HTTPSystemCertsAdapter())
|
|
88
|
+
return session.get(url, params=params, **kwargs)
|
|
89
|
+
else:
|
|
90
|
+
try:
|
|
91
|
+
return requests.get(url, params=params, **kwargs)
|
|
92
|
+
except requests.exceptions.SSLError as ex:
|
|
93
|
+
if "SSLCertVerificationError" not in str(ex):
|
|
94
|
+
raise ex
|
|
95
|
+
USE_SYSTEM_CERTS = True
|
|
96
|
+
# HTTPSConnectionPool(host='graph.mapillary.com', port=443): Max retries exceeded with url: /login (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1018)')))
|
|
97
|
+
LOG.warning(
|
|
98
|
+
"SSL error occurred, falling back to system SSL certificates: %s", ex
|
|
99
|
+
)
|
|
100
|
+
with requests.Session() as session:
|
|
101
|
+
session.mount("https://", HTTPSystemCertsAdapter())
|
|
102
|
+
return session.get(url, params=params, **kwargs)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def get_upload_token(email: str, password: str) -> requests.Response:
|
|
106
|
+
resp = request_post(
|
|
107
|
+
f"{MAPILLARY_GRAPH_API_ENDPOINT}/login",
|
|
108
|
+
params={"access_token": MAPILLARY_CLIENT_TOKEN},
|
|
109
|
+
json={"email": email, "password": password, "locale": "en_US"},
|
|
110
|
+
timeout=REQUESTS_TIMEOUT,
|
|
111
|
+
)
|
|
112
|
+
resp.raise_for_status()
|
|
113
|
+
return resp
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def fetch_organization(
|
|
117
|
+
user_access_token: str, organization_id: T.Union[int, str]
|
|
118
|
+
) -> requests.Response:
|
|
119
|
+
resp = request_get(
|
|
120
|
+
f"{MAPILLARY_GRAPH_API_ENDPOINT}/{organization_id}",
|
|
121
|
+
params={
|
|
122
|
+
"fields": ",".join(["slug", "description", "name"]),
|
|
123
|
+
},
|
|
124
|
+
headers={
|
|
125
|
+
"Authorization": f"OAuth {user_access_token}",
|
|
126
|
+
},
|
|
127
|
+
timeout=REQUESTS_TIMEOUT,
|
|
128
|
+
)
|
|
129
|
+
resp.raise_for_status()
|
|
130
|
+
return resp
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
ActionType = T.Literal[
|
|
134
|
+
"upload_started_upload", "upload_finished_upload", "upload_failed_upload"
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def log_event(action_type: ActionType, properties: T.Dict) -> requests.Response:
|
|
139
|
+
resp = request_post(
|
|
140
|
+
f"{MAPILLARY_GRAPH_API_ENDPOINT}/logging",
|
|
141
|
+
json={
|
|
142
|
+
"action_type": action_type,
|
|
143
|
+
"properties": properties,
|
|
144
|
+
},
|
|
145
|
+
headers={
|
|
146
|
+
"Authorization": f"OAuth {MAPILLARY_CLIENT_TOKEN}",
|
|
147
|
+
},
|
|
148
|
+
timeout=REQUESTS_TIMEOUT,
|
|
149
|
+
)
|
|
150
|
+
resp.raise_for_status()
|
|
151
|
+
return resp
|
|
@@ -2,60 +2,46 @@ import io
|
|
|
2
2
|
import typing as T
|
|
3
3
|
|
|
4
4
|
from .. import geo, types
|
|
5
|
-
|
|
6
|
-
from . import (
|
|
7
|
-
camm_parser,
|
|
5
|
+
from ..mp4 import (
|
|
8
6
|
construct_mp4_parser as cparser,
|
|
9
7
|
mp4_sample_parser as sample_parser,
|
|
10
8
|
simple_mp4_builder as builder,
|
|
11
9
|
)
|
|
12
|
-
from .simple_mp4_builder import BoxDict
|
|
13
10
|
|
|
11
|
+
from . import camm_parser
|
|
14
12
|
|
|
15
|
-
def build_camm_sample(point: geo.Point) -> bytes:
|
|
16
|
-
return camm_parser.CAMMSampleData.build(
|
|
17
|
-
{
|
|
18
|
-
"type": camm_parser.CAMMType.MIN_GPS.value,
|
|
19
|
-
"data": [
|
|
20
|
-
point.lat,
|
|
21
|
-
point.lon,
|
|
22
|
-
-1.0 if point.alt is None else point.alt,
|
|
23
|
-
],
|
|
24
|
-
}
|
|
25
|
-
)
|
|
26
13
|
|
|
14
|
+
def _build_camm_sample(measurement: camm_parser.TelemetryMeasurement) -> bytes:
|
|
15
|
+
for sample_entry_cls in camm_parser.SAMPLE_ENTRY_CLS_BY_CAMM_TYPE.values():
|
|
16
|
+
if sample_entry_cls.serializable(measurement):
|
|
17
|
+
return sample_entry_cls.serialize(measurement)
|
|
18
|
+
raise ValueError(f"Unsupported measurement type {type(measurement)}")
|
|
27
19
|
|
|
28
|
-
|
|
20
|
+
|
|
21
|
+
def _create_edit_list_from_points(
|
|
29
22
|
point_segments: T.Sequence[T.Sequence[geo.Point]],
|
|
30
23
|
movie_timescale: int,
|
|
31
24
|
media_timescale: int,
|
|
32
|
-
) -> BoxDict:
|
|
25
|
+
) -> builder.BoxDict:
|
|
33
26
|
entries: T.List[T.Dict] = []
|
|
34
27
|
|
|
35
|
-
|
|
36
|
-
if not points:
|
|
37
|
-
entries = [
|
|
38
|
-
{
|
|
39
|
-
"media_time": 0,
|
|
40
|
-
"segment_duration": 0,
|
|
41
|
-
"media_rate_integer": 1,
|
|
42
|
-
"media_rate_fraction": 0,
|
|
43
|
-
}
|
|
44
|
-
]
|
|
45
|
-
break
|
|
28
|
+
non_empty_point_segments = [points for points in point_segments if points]
|
|
46
29
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
30
|
+
for idx, points in enumerate(non_empty_point_segments):
|
|
31
|
+
assert 0 <= points[0].time, (
|
|
32
|
+
f"expect non-negative point time but got {points[0]}"
|
|
33
|
+
)
|
|
34
|
+
assert points[0].time <= points[-1].time, (
|
|
35
|
+
f"expect points to be sorted but got first point {points[0]} and last point {points[-1]}"
|
|
36
|
+
)
|
|
53
37
|
|
|
54
38
|
if idx == 0:
|
|
55
39
|
if 0 < points[0].time:
|
|
56
40
|
segment_duration = int(points[0].time * movie_timescale)
|
|
41
|
+
# put an empty edit list entry to skip the initial gap
|
|
57
42
|
entries.append(
|
|
58
43
|
{
|
|
44
|
+
# If this field is set to –1, it is an empty edit
|
|
59
45
|
"media_time": -1,
|
|
60
46
|
"segment_duration": segment_duration,
|
|
61
47
|
"media_rate_integer": 1,
|
|
@@ -63,7 +49,6 @@ def _create_edit_list(
|
|
|
63
49
|
}
|
|
64
50
|
)
|
|
65
51
|
else:
|
|
66
|
-
assert point_segments[-1][-1].time <= points[0].time
|
|
67
52
|
media_time = int(points[0].time * media_timescale)
|
|
68
53
|
segment_duration = int((points[-1].time - points[0].time) * movie_timescale)
|
|
69
54
|
entries.append(
|
|
@@ -83,19 +68,34 @@ def _create_edit_list(
|
|
|
83
68
|
}
|
|
84
69
|
|
|
85
70
|
|
|
86
|
-
def
|
|
87
|
-
points: T.Sequence[geo.Point],
|
|
71
|
+
def _multiplex(
|
|
72
|
+
points: T.Sequence[geo.Point],
|
|
73
|
+
measurements: T.Optional[T.List[camm_parser.TelemetryMeasurement]] = None,
|
|
74
|
+
) -> T.List[camm_parser.TelemetryMeasurement]:
|
|
75
|
+
mutiplexed: T.List[camm_parser.TelemetryMeasurement] = [
|
|
76
|
+
*points,
|
|
77
|
+
*(measurements or []),
|
|
78
|
+
]
|
|
79
|
+
mutiplexed.sort(key=lambda m: m.time)
|
|
80
|
+
|
|
81
|
+
return mutiplexed
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def convert_telemetry_to_raw_samples(
|
|
85
|
+
measurements: T.Sequence[camm_parser.TelemetryMeasurement],
|
|
86
|
+
timescale: int,
|
|
88
87
|
) -> T.Generator[sample_parser.RawSample, None, None]:
|
|
89
|
-
for idx,
|
|
90
|
-
camm_sample_data =
|
|
88
|
+
for idx, measurement in enumerate(measurements):
|
|
89
|
+
camm_sample_data = _build_camm_sample(measurement)
|
|
91
90
|
|
|
92
|
-
if idx + 1 < len(
|
|
93
|
-
timedelta = int((
|
|
91
|
+
if idx + 1 < len(measurements):
|
|
92
|
+
timedelta = int((measurements[idx + 1].time - measurement.time) * timescale)
|
|
94
93
|
else:
|
|
95
94
|
timedelta = 0
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
|
|
96
|
+
assert 0 <= timedelta <= builder.UINT32_MAX, (
|
|
97
|
+
f"expected timedelta {timedelta} between {measurements[idx]} and {measurements[idx + 1]} with timescale {timescale} to be <= UINT32_MAX"
|
|
98
|
+
)
|
|
99
99
|
|
|
100
100
|
yield sample_parser.RawSample(
|
|
101
101
|
# will update later
|
|
@@ -114,7 +114,9 @@ _STBLChildrenBuilderConstruct = cparser.Box32ConstructBuilder(
|
|
|
114
114
|
)
|
|
115
115
|
|
|
116
116
|
|
|
117
|
-
def _create_camm_stbl(
|
|
117
|
+
def _create_camm_stbl(
|
|
118
|
+
raw_samples: T.Iterable[sample_parser.RawSample],
|
|
119
|
+
) -> builder.BoxDict:
|
|
118
120
|
descriptions = [
|
|
119
121
|
{
|
|
120
122
|
"format": b"camm",
|
|
@@ -135,10 +137,10 @@ def _create_camm_stbl(raw_samples: T.Iterable[sample_parser.RawSample]) -> BoxDi
|
|
|
135
137
|
def create_camm_trak(
|
|
136
138
|
raw_samples: T.Sequence[sample_parser.RawSample],
|
|
137
139
|
media_timescale: int,
|
|
138
|
-
) -> BoxDict:
|
|
140
|
+
) -> builder.BoxDict:
|
|
139
141
|
stbl = _create_camm_stbl(raw_samples)
|
|
140
142
|
|
|
141
|
-
hdlr: BoxDict = {
|
|
143
|
+
hdlr: builder.BoxDict = {
|
|
142
144
|
"type": b"hdlr",
|
|
143
145
|
"data": {
|
|
144
146
|
"handler_type": b"camm",
|
|
@@ -150,7 +152,7 @@ def create_camm_trak(
|
|
|
150
152
|
assert media_timescale <= builder.UINT64_MAX
|
|
151
153
|
|
|
152
154
|
# Media Header Box
|
|
153
|
-
mdhd: BoxDict = {
|
|
155
|
+
mdhd: builder.BoxDict = {
|
|
154
156
|
"type": b"mdhd",
|
|
155
157
|
"data": {
|
|
156
158
|
# use 64-bit version
|
|
@@ -166,7 +168,7 @@ def create_camm_trak(
|
|
|
166
168
|
},
|
|
167
169
|
}
|
|
168
170
|
|
|
169
|
-
dinf: BoxDict = {
|
|
171
|
+
dinf: builder.BoxDict = {
|
|
170
172
|
"type": b"dinf",
|
|
171
173
|
"data": [
|
|
172
174
|
# self reference dref box
|
|
@@ -187,7 +189,7 @@ def create_camm_trak(
|
|
|
187
189
|
],
|
|
188
190
|
}
|
|
189
191
|
|
|
190
|
-
minf: BoxDict = {
|
|
192
|
+
minf: builder.BoxDict = {
|
|
191
193
|
"type": b"minf",
|
|
192
194
|
"data": [
|
|
193
195
|
dinf,
|
|
@@ -195,7 +197,7 @@ def create_camm_trak(
|
|
|
195
197
|
],
|
|
196
198
|
}
|
|
197
199
|
|
|
198
|
-
tkhd: BoxDict = {
|
|
200
|
+
tkhd: builder.BoxDict = {
|
|
199
201
|
"type": b"tkhd",
|
|
200
202
|
"data": {
|
|
201
203
|
# use 32-bit version of the box
|
|
@@ -213,7 +215,7 @@ def create_camm_trak(
|
|
|
213
215
|
},
|
|
214
216
|
}
|
|
215
217
|
|
|
216
|
-
mdia: BoxDict = {
|
|
218
|
+
mdia: builder.BoxDict = {
|
|
217
219
|
"type": b"mdia",
|
|
218
220
|
"data": [
|
|
219
221
|
mdhd,
|
|
@@ -231,23 +233,31 @@ def create_camm_trak(
|
|
|
231
233
|
}
|
|
232
234
|
|
|
233
235
|
|
|
234
|
-
def camm_sample_generator2(
|
|
236
|
+
def camm_sample_generator2(
|
|
237
|
+
video_metadata: types.VideoMetadata,
|
|
238
|
+
telemetry_measurements: T.Optional[T.List[camm_parser.TelemetryMeasurement]] = None,
|
|
239
|
+
):
|
|
235
240
|
def _f(
|
|
236
241
|
fp: T.BinaryIO,
|
|
237
|
-
moov_children: T.List[BoxDict],
|
|
242
|
+
moov_children: T.List[builder.BoxDict],
|
|
238
243
|
) -> T.Generator[io.IOBase, None, None]:
|
|
239
244
|
movie_timescale = builder.find_movie_timescale(moov_children)
|
|
240
245
|
# make sure the precision of timedeltas not lower than 0.001 (1ms)
|
|
241
246
|
media_timescale = max(1000, movie_timescale)
|
|
247
|
+
|
|
248
|
+
# points with negative time are skipped
|
|
249
|
+
# TODO: interpolate first point at time == 0
|
|
250
|
+
# TODO: measurements with negative times should be skipped too
|
|
251
|
+
points = [point for point in video_metadata.points if point.time >= 0]
|
|
252
|
+
|
|
253
|
+
measurements = _multiplex(points, telemetry_measurements)
|
|
242
254
|
camm_samples = list(
|
|
243
|
-
|
|
255
|
+
convert_telemetry_to_raw_samples(measurements, media_timescale)
|
|
244
256
|
)
|
|
245
257
|
camm_trak = create_camm_trak(camm_samples, media_timescale)
|
|
246
|
-
elst =
|
|
247
|
-
[video_metadata.points], movie_timescale, media_timescale
|
|
248
|
-
)
|
|
258
|
+
elst = _create_edit_list_from_points([points], movie_timescale, media_timescale)
|
|
249
259
|
if T.cast(T.Dict, elst["data"])["entries"]:
|
|
250
|
-
T.cast(T.List[BoxDict], camm_trak["data"]).append(
|
|
260
|
+
T.cast(T.List[builder.BoxDict], camm_trak["data"]).append(
|
|
251
261
|
{
|
|
252
262
|
"type": b"edts",
|
|
253
263
|
"data": [elst],
|
|
@@ -255,7 +265,7 @@ def camm_sample_generator2(video_metadata: types.VideoMetadata):
|
|
|
255
265
|
)
|
|
256
266
|
moov_children.append(camm_trak)
|
|
257
267
|
|
|
258
|
-
udta_data: T.List[BoxDict] = []
|
|
268
|
+
udta_data: T.List[builder.BoxDict] = []
|
|
259
269
|
if video_metadata.make:
|
|
260
270
|
udta_data.append(
|
|
261
271
|
{
|
|
@@ -279,6 +289,8 @@ def camm_sample_generator2(video_metadata: types.VideoMetadata):
|
|
|
279
289
|
)
|
|
280
290
|
|
|
281
291
|
# if yield, the moov_children will not be modified
|
|
282
|
-
return (
|
|
292
|
+
return (
|
|
293
|
+
io.BytesIO(_build_camm_sample(measurement)) for measurement in measurements
|
|
294
|
+
)
|
|
283
295
|
|
|
284
296
|
return _f
|