Skip to content

Commit 4fac5d8

Browse files
authored
chore: fix test cli (#730)
* fix geotag_videos * fix * check types * chore * fix * fix * usort
1 parent 3fd61c2 commit 4fac5d8

File tree

7 files changed

+129
-88
lines changed

7 files changed

+129
-88
lines changed

.github/workflows/python-package.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ jobs:
8181
8282
- name: Type check with mypy
8383
run: |
84-
mypy mapillary_tools
84+
mypy mapillary_tools tests/cli
8585
8686
- name: Test with pytest
8787
run: |

tests/cli/blackvue_parser.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,27 @@
1+
from __future__ import annotations
2+
13
import argparse
24
import pathlib
35

46
import gpxpy
57
import gpxpy.gpx
68

7-
from mapillary_tools import utils
9+
from mapillary_tools import geo, utils
810
from mapillary_tools.geotag import blackvue_parser, utils as geotag_utils
911

1012

13+
def _parse_gpx(path: pathlib.Path) -> list[geo.Point] | None:
14+
with path.open("rb") as fp:
15+
points = blackvue_parser.extract_points(fp)
16+
return points
17+
18+
1119
def _convert_to_track(path: pathlib.Path):
1220
track = gpxpy.gpx.GPXTrack()
13-
points = blackvue_parser.parse_gps_points(path)
21+
points = _parse_gpx(path)
22+
if points is None:
23+
raise RuntimeError(f"Invalid BlackVue video {path}")
24+
1425
segment = geotag_utils.convert_points_to_gpx_segment(points)
1526
track.segments.append(segment)
1627
with path.open("rb") as fp:

tests/cli/camm_parser.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,16 @@
1111
from mapillary_tools.geotag import utils as geotag_utils
1212

1313

14+
def _parse_gpx(path: pathlib.Path):
15+
with path.open("rb") as fp:
16+
return camm_parser.extract_points(fp)
17+
18+
1419
def _convert(path: pathlib.Path):
15-
points = camm_parser.parse_gpx(path)
20+
points = _parse_gpx(path)
21+
if points is None:
22+
raise RuntimeError(f"Invalid CAMM video {path}")
23+
1624
track = gpxpy.gpx.GPXTrack()
1725
track.name = path.name
1826
track.segments.append(geotag_utils.convert_points_to_gpx_segment(points))

tests/cli/exif_read.py

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,19 @@ def extract_and_show_exif(image_path):
2525

2626

2727
def as_dict(exif: ExifReadABC):
28+
if isinstance(exif, (ExifToolRead, ExifRead)):
29+
gps_datetime = exif.extract_gps_datetime()
30+
exif_time = exif.extract_exif_datetime()
31+
else:
32+
gps_datetime = None
33+
exif_time = None
34+
2835
return {
2936
"altitude": exif.extract_altitude(),
3037
"capture_time": exif.extract_capture_time(),
3138
"direction": exif.extract_direction(),
32-
"exif_time": exif.extract_exif_datetime(),
33-
"gps_time": exif.extract_gps_datetime(),
39+
"exif_time": exif_time,
40+
"gps_time": gps_datetime,
3441
"lon_lat": exif.extract_lon_lat(),
3542
"make": exif.extract_make(),
3643
"model": exif.extract_model(),
@@ -40,32 +47,28 @@ def as_dict(exif: ExifReadABC):
4047
}
4148

4249

43-
def _round(v):
44-
if isinstance(v, float):
45-
return round(v, 6)
46-
elif isinstance(v, tuple):
47-
return tuple(round(x, 6) for x in v)
48-
else:
49-
return v
50+
def _approximate(left, right):
51+
if isinstance(left, float) and isinstance(right, float):
52+
return abs(left - right) < 0.000001
53+
if isinstance(left, tuple) and isinstance(right, tuple):
54+
return all(abs(l - r) < 0.000001 for l, r in zip(left, right))
55+
return left == right
5056

5157

52-
def compare_exif(left: dict, right: dict) -> bool:
58+
def compare_exif(left: dict, right: dict) -> str:
5359
RED_COLOR = "\x1b[31;20m"
5460
RESET_COLOR = "\x1b[0m"
55-
diff = False
61+
diff = []
5662
for key in left:
5763
if key in ["width", "height"]:
5864
continue
5965
if key in ["lon_lat", "altitude", "direction"]:
60-
left_value = _round(left[key])
61-
right_value = _round(right[key])
66+
same = _approximate(left[key], right[key])
6267
else:
63-
left_value = left[key]
64-
right_value = right[key]
65-
if left_value != right_value:
66-
print(f"{RED_COLOR}{key}: {left_value} != {right_value}{RESET_COLOR}")
67-
diff = True
68-
return diff
68+
same = left[key] == right[key]
69+
if not same:
70+
diff.append(f"{RED_COLOR}{key}: {left[key]} != {right[key]}{RESET_COLOR}")
71+
return "\n".join(diff)
6972

7073

7174
def extract_and_show_from_exiftool(fp, compare: bool = False):
@@ -82,10 +85,19 @@ def extract_and_show_from_exiftool(fp, compare: bool = False):
8285
native_exif = ExifRead(image_path)
8386
diff = compare_exif(as_dict(exif), as_dict(native_exif))
8487
if diff:
85-
print(f"======== ExifTool Outuput {image_path} ========")
88+
print(f"======== {image_path} ========")
89+
90+
print("ExifTool Outuput:")
8691
pprint.pprint(as_dict(exif))
87-
print(f"======== ExifRead Output {image_path} ========")
92+
print()
93+
94+
print("ExifRead Output:")
8895
pprint.pprint(as_dict(native_exif))
96+
print()
97+
98+
print("DIFF:")
99+
print(diff)
100+
print()
89101
else:
90102
print(f"======== ExifTool Outuput {image_path} ========")
91103
pprint.pprint(as_dict(exif))

tests/cli/gpmf_parser.py

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
import argparse
24
import dataclasses
35
import datetime
@@ -66,27 +68,39 @@ def _convert_points_to_gpx_track_segment(
6668
return gpx_segment
6769

6870

71+
def _parse_gpx(path: pathlib.Path) -> list[telemetry.GPSPoint] | None:
72+
with path.open("rb") as fp:
73+
info = gpmf_parser.extract_gopro_info(fp)
74+
if info is None:
75+
return None
76+
return info.gps or []
77+
78+
6979
def _convert_gpx(gpx: gpxpy.gpx.GPX, path: pathlib.Path):
70-
points = gpmf_parser.parse_gpx(path)
80+
points = _parse_gpx(path)
81+
if points is None:
82+
raise RuntimeError(f"Invalid GoPro video {path}")
7183
gpx_track = gpxpy.gpx.GPXTrack()
7284
gpx_track_segment = _convert_points_to_gpx_track_segment(points)
7385
gpx_track.segments.append(gpx_track_segment)
7486
gpx_track.name = path.name
7587
gpx_track.comment = f"#points: {len(points)}"
7688
with path.open("rb") as fp:
77-
device_names = gpmf_parser.extract_all_device_names(fp)
78-
with path.open("rb") as fp:
79-
model = gpmf_parser.extract_camera_model(fp)
89+
info = gpmf_parser.extract_gopro_info(fp)
90+
if info is None:
91+
return
8092
gpx_track.description = (
81-
f'Extracted from model "{model}" among these devices {device_names}'
93+
f'Extracted from model "{info.model}" and make "{info.make}"'
8294
)
8395
gpx.tracks.append(gpx_track)
8496

8597

8698
def _convert_geojson(path: pathlib.Path):
87-
features = []
88-
points = gpmf_parser.parse_gpx(path)
99+
points = _parse_gpx(path)
100+
if points is None:
101+
raise RuntimeError(f"Invalid GoPro video {path}")
89102

103+
features = []
90104
for idx, p in enumerate(points):
91105
geomtry = {"type": "Point", "coordinates": [p.lon, p.lat]}
92106
properties = {
@@ -138,24 +152,33 @@ def main():
138152
imu_option = parsed_args.imu.split(",")
139153
for path in video_paths:
140154
with path.open("rb") as fp:
141-
telemetry_data = gpmf_parser.extract_telemetry_data(fp)
155+
telemetry_data = gpmf_parser.extract_gopro_info(fp, telemetry_only=True)
142156
if telemetry_data:
143157
if "accl" in imu_option:
144158
print(
145159
json.dumps(
146-
[dataclasses.asdict(accl) for accl in telemetry_data.accl]
160+
[
161+
dataclasses.asdict(accl)
162+
for accl in telemetry_data.accl or []
163+
]
147164
)
148165
)
149166
if "gyro" in imu_option:
150167
print(
151168
json.dumps(
152-
[dataclasses.asdict(gyro) for gyro in telemetry_data.gyro]
169+
[
170+
dataclasses.asdict(gyro)
171+
for gyro in telemetry_data.gyro or []
172+
]
153173
)
154174
)
155175
if "magn" in imu_option:
156176
print(
157177
json.dumps(
158-
[dataclasses.asdict(magn) for magn in telemetry_data.magn]
178+
[
179+
dataclasses.asdict(magn)
180+
for magn in telemetry_data.magn or []
181+
]
159182
)
160183
)
161184

tests/cli/simple_mp4_parser.py

Lines changed: 35 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
import argparse
24
import io
35
import logging
@@ -31,40 +33,29 @@
3133
}
3234

3335

34-
def _validate_samples(
35-
path: pathlib.Path, filters: T.Optional[T.Container[bytes]] = None
36-
):
37-
samples: T.List[sample_parser.RawSample] = []
38-
39-
with open(path, "rb") as fp:
40-
for h, s in sparser.parse_path(
41-
fp, [b"moov", b"trak", b"mdia", b"minf", b"stbl"]
42-
):
43-
(
44-
descriptions,
45-
raw_samples,
46-
) = sample_parser.parse_raw_samples_from_stbl_DEPRECATED(
47-
s, maxsize=h.maxsize
48-
)
49-
samples.extend(
50-
sample
51-
for sample in raw_samples
52-
if filters is None
53-
or descriptions[sample.description_idx]["format"] in filters
54-
)
55-
samples.sort(key=lambda s: s.offset)
56-
if not samples:
36+
def _validate_samples(path: pathlib.Path, filters: T.Container[bytes] | None = None):
37+
raw_samples: list[sample_parser.RawSample] = []
38+
39+
parser = sample_parser.MovieBoxParser.parse_file(path)
40+
for track in parser.extract_tracks():
41+
for sample in track.extract_samples():
42+
if filters is None or sample.description["format"] in filters:
43+
raw_samples.append(sample.raw_sample)
44+
45+
raw_samples.sort(key=lambda s: s.offset)
46+
if not raw_samples:
5747
return
48+
5849
last_sample = None
59-
last_read = samples[0].offset
60-
for sample in samples:
61-
if sample.offset < last_read:
50+
last_read = raw_samples[0].offset
51+
for raw_sample in raw_samples:
52+
if raw_sample.offset < last_read:
6253
LOG.warning(f"overlap found:\n{last_sample}\n{sample}")
63-
elif sample.offset == last_read:
54+
elif raw_sample.offset == last_read:
6455
pass
6556
else:
6657
LOG.warning(f"gap found:\n{last_sample}\n{sample}")
67-
last_read = sample.offset + sample.size
58+
last_read = raw_sample.offset + raw_sample.size
6859
last_sample = sample
6960

7061

@@ -87,7 +78,7 @@ def _parse_structs(fp: T.BinaryIO):
8778
print(margin, header, data)
8879

8980

90-
def _dump_box_data_at(fp: T.BinaryIO, box_type_path: T.List[bytes]):
81+
def _dump_box_data_at(fp: T.BinaryIO, box_type_path: list[bytes]):
9182
for h, s in sparser.parse_path(fp, box_type_path):
9283
max_chunk_size = 1024
9384
read = 0
@@ -104,30 +95,26 @@ def _dump_box_data_at(fp: T.BinaryIO, box_type_path: T.List[bytes]):
10495
break
10596

10697

107-
def _parse_samples(fp: T.BinaryIO, filters: T.Optional[T.Container[bytes]] = None):
108-
for h, s in sparser.parse_path(fp, [b"moov", b"trak"]):
109-
offset = s.tell()
110-
for h1, s1 in sparser.parse_path(s, [b"mdia", b"mdhd"], maxsize=h.maxsize):
111-
box = cparser.MediaHeaderBox.parse(s1.read(h.maxsize))
112-
LOG.info(box)
113-
LOG.info(sample_parser.to_datetime(box.creation_time))
114-
LOG.info(box.duration / box.timescale)
115-
s.seek(offset, io.SEEK_SET)
116-
for sample in sample_parser.parse_samples_from_trak_DEPRECATED(
117-
s, maxsize=h.maxsize
118-
):
98+
def _parse_samples(fp: T.BinaryIO, filters: T.Container[bytes] | None = None):
99+
parser = sample_parser.MovieBoxParser.parse_stream(fp)
100+
for track in parser.extract_tracks():
101+
box = track.extract_mdhd_boxdata()
102+
LOG.info(box)
103+
LOG.info(sample_parser.to_datetime(box["creation_time"]))
104+
LOG.info(box["duration"] / box["timescale"])
105+
106+
for sample in track.extract_samples():
119107
if filters is None or sample.description["format"] in filters:
120108
print(sample)
121109

122110

123-
def _dump_samples(fp: T.BinaryIO, filters: T.Optional[T.Container[bytes]] = None):
124-
for h, s in sparser.parse_path(fp, [b"moov", b"trak"]):
125-
for sample in sample_parser.parse_samples_from_trak_DEPRECATED(
126-
s, maxsize=h.maxsize
127-
):
111+
def _dump_samples(fp: T.BinaryIO, filters: T.Container[bytes] | None = None):
112+
parser = sample_parser.MovieBoxParser.parse_stream(fp)
113+
for track in parser.extract_tracks():
114+
for sample in track.extract_samples():
128115
if filters is None or sample.description["format"] in filters:
129-
fp.seek(sample.offset, io.SEEK_SET)
130-
data = fp.read(sample.size)
116+
fp.seek(sample.raw_sample.offset, io.SEEK_SET)
117+
data = fp.read(sample.raw_sample.size)
131118
sys.stdout.buffer.write(data)
132119

133120

tests/cli/upload_api_v4.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,10 @@ def main():
8383
)
8484
initial_offset = service.fetch_offset()
8585

86-
LOG.info(f"Session key: %s", session_key)
87-
LOG.info(f"Entity size: %d", entity_size)
88-
LOG.info(f"Initial offset: %s", initial_offset)
89-
LOG.info(f"Chunk size: %s MB", service.chunk_size / (1024 * 1024))
86+
LOG.info("Session key: %s", session_key)
87+
LOG.info("Entity size: %d", entity_size)
88+
LOG.info("Initial offset: %s", initial_offset)
89+
LOG.info("Chunk size: %s MB", service.chunk_size / (1024 * 1024))
9090

9191
with open(parsed.filename, "rb") as fp:
9292
with tqdm.tqdm(

0 commit comments

Comments
 (0)