1+ from __future__ import annotations
2+
3+ import dataclasses
4+
15import json
26import logging
37import re
48import typing as T
59
610import pynmea2
711
8- from .. import geo
9- from .. mp4 import simple_mp4_parser as sparser
12+ from . import geo
13+ from .mp4 import simple_mp4_parser as sparser
1014
1115
1216LOG = logging .getLogger (__name__ )
2529)
2630
2731
28- def _parse_gps_box (gps_data : bytes ) -> T .Generator [geo .Point , None , None ]:
29- for line_bytes in gps_data .splitlines ():
30- match = NMEA_LINE_REGEX .match (line_bytes )
31- if match is None :
32- continue
33- nmea_line_bytes = match .group (2 )
34- if nmea_line_bytes .startswith (b"$GPGGA" ):
35- try :
36- nmea_line = nmea_line_bytes .decode ("utf8" )
37- except UnicodeDecodeError :
38- continue
39- try :
40- nmea = pynmea2 .parse (nmea_line )
41- except pynmea2 .nmea .ParseError :
42- continue
43- if not nmea .is_valid :
44- continue
45- epoch_ms = int (match .group (1 ))
46- yield geo .Point (
47- time = epoch_ms ,
48- lat = nmea .latitude ,
49- lon = nmea .longitude ,
50- alt = nmea .altitude ,
51- angle = None ,
52- )
32+ @dataclasses .dataclass
33+ class BlackVueInfo :
34+ # None and [] are equivalent here. Use None as default because:
35+ # ValueError: mutable default <class 'list'> for field gps is not allowed: use default_factory
36+ gps : list [geo .Point ] | None = None
37+ make : str = "BlackVue"
38+ model : str = ""
39+
40+
41+ def extract_blackvue_info (fp : T .BinaryIO ) -> BlackVueInfo | None :
42+ try :
43+ gps_data = sparser .parse_mp4_data_first (fp , [b"free" , b"gps " ])
44+ except sparser .ParsingError :
45+ gps_data = None
46+
47+ if gps_data is None :
48+ return None
49+
50+ points = list (_parse_gps_box (gps_data ))
51+ points .sort (key = lambda p : p .time )
52+
53+ if points :
54+ first_point_time = points [0 ].time
55+ for p in points :
56+ p .time = (p .time - first_point_time ) / 1000
57+
58+ # Camera model
59+ try :
60+ cprt_bytes = sparser .parse_mp4_data_first (fp , [b"free" , b"cprt" ])
61+ except sparser .ParsingError :
62+ cprt_bytes = None
63+ model = ""
64+
65+ if cprt_bytes is None :
66+ model = ""
67+ else :
68+ model = _extract_camera_model_from_cprt (cprt_bytes )
69+
70+ return BlackVueInfo (model = model , gps = points )
5371
5472
5573def extract_camera_model (fp : T .BinaryIO ) -> str :
@@ -61,6 +79,10 @@ def extract_camera_model(fp: T.BinaryIO) -> str:
6179 if cprt_bytes is None :
6280 return ""
6381
82+ return _extract_camera_model_from_cprt (cprt_bytes )
83+
84+
85+ def _extract_camera_model_from_cprt (cprt_bytes : bytes ) -> str :
6486 # examples: b' {"model":"DR900X Plus","ver":0.918,"lang":"English","direct":1,"psn":"","temp":34,"GPS":1}\x00'
6587 # b' Pittasoft Co., Ltd.;DR900S-1CH;1.008;English;1;D90SS1HAE00661;T69;\x00'
6688 cprt_bytes = cprt_bytes .strip ().strip (b"\x00 " )
@@ -89,19 +111,28 @@ def extract_camera_model(fp: T.BinaryIO) -> str:
89111 return ""
90112
91113
92- def extract_points (fp : T .BinaryIO ) -> T .Optional [T .List [geo .Point ]]:
93- gps_data = sparser .parse_mp4_data_first (fp , [b"free" , b"gps " ])
94- if gps_data is None :
95- return None
96-
97- points = list (_parse_gps_box (gps_data ))
98- if not points :
99- return points
100-
101- points .sort (key = lambda p : p .time )
102-
103- first_point_time = points [0 ].time
104- for p in points :
105- p .time = (p .time - first_point_time ) / 1000
106-
107- return points
114+ def _parse_gps_box (gps_data : bytes ) -> T .Generator [geo .Point , None , None ]:
115+ for line_bytes in gps_data .splitlines ():
116+ match = NMEA_LINE_REGEX .match (line_bytes )
117+ if match is None :
118+ continue
119+ nmea_line_bytes = match .group (2 )
120+ if nmea_line_bytes .startswith (b"$GPGGA" ):
121+ try :
122+ nmea_line = nmea_line_bytes .decode ("utf8" )
123+ except UnicodeDecodeError :
124+ continue
125+ try :
126+ nmea = pynmea2 .parse (nmea_line )
127+ except pynmea2 .nmea .ParseError :
128+ continue
129+ if not nmea .is_valid :
130+ continue
131+ epoch_ms = int (match .group (1 ))
132+ yield geo .Point (
133+ time = epoch_ms ,
134+ lat = nmea .latitude ,
135+ lon = nmea .longitude ,
136+ alt = nmea .altitude ,
137+ angle = None ,
138+ )
0 commit comments