pyrekordbox 0.4.2__py3-none-any.whl → 0.4.3__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.
pyrekordbox/__init__.py CHANGED
@@ -2,6 +2,7 @@
2
2
  # Author: Dylan Jones
3
3
  # Date: 2022-04-10
4
4
 
5
+ # mypy: disable-error-code="attr-defined"
5
6
  from .anlz import AnlzFile, get_anlz_paths, read_anlz_files, walk_anlz_paths
6
7
  from .config import get_config, show_config, update_config
7
8
  from .db6 import Rekordbox6Database
pyrekordbox/_version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.4.2'
21
- __version_tuple__ = version_tuple = (0, 4, 2)
20
+ __version__ = version = '0.4.3'
21
+ __version_tuple__ = version_tuple = (0, 4, 3)
@@ -4,7 +4,7 @@
4
4
 
5
5
  import re
6
6
  from pathlib import Path
7
- from typing import Union
7
+ from typing import Dict, Iterator, Tuple, Union
8
8
 
9
9
  from . import structs
10
10
  from .file import AnlzFile
@@ -42,7 +42,7 @@ def is_anlz_file(path: Union[str, Path]) -> bool:
42
42
  return bool(RE_ANLZ.match(path.name))
43
43
 
44
44
 
45
- def get_anlz_paths(root: Union[str, Path]) -> dict:
45
+ def get_anlz_paths(root: Union[str, Path]) -> Dict[str, Union[Path, None]]:
46
46
  """Returns the paths of all existing ANLZ files in a directory.
47
47
 
48
48
  Parameters
@@ -62,14 +62,14 @@ def get_anlz_paths(root: Union[str, Path]) -> dict:
62
62
  >>> p["DAT"]
63
63
  directory/ANLZ0000.DAT
64
64
  """
65
- paths = {"DAT": None, "EXT": None, "2EX": None}
65
+ paths: Dict[str, Union[Path, None]] = {"DAT": None, "EXT": None, "2EX": None}
66
66
  for path in Path(root).iterdir():
67
67
  if RE_ANLZ.match(path.name):
68
68
  paths[path.suffix[1:].upper()] = path
69
69
  return paths
70
70
 
71
71
 
72
- def walk_anlz_dirs(root_dir: Union[str, Path]):
72
+ def walk_anlz_dirs(root_dir: Union[str, Path]) -> Iterator[Path]:
73
73
  """Finds all ANLZ directory paths recursively.
74
74
 
75
75
  Parameters
@@ -88,7 +88,7 @@ def walk_anlz_dirs(root_dir: Union[str, Path]):
88
88
  yield path
89
89
 
90
90
 
91
- def walk_anlz_paths(root_dir: Union[str, Path]):
91
+ def walk_anlz_paths(root_dir: Union[str, Path]) -> Iterator[Tuple[Path, Dict[str, Path]]]:
92
92
  """Finds all ANLZ directory paths and the containing file paths recursively.
93
93
 
94
94
  Parameters
@@ -110,7 +110,7 @@ def walk_anlz_paths(root_dir: Union[str, Path]):
110
110
  yield anlz_dir, files
111
111
 
112
112
 
113
- def read_anlz_files(root: Union[str, Path] = "") -> dict:
113
+ def read_anlz_files(root: Union[str, Path] = "") -> Dict[Path, AnlzFile]:
114
114
  """Open all ANLZ files in the given root directory.
115
115
 
116
116
  Parameters
pyrekordbox/anlz/file.py CHANGED
@@ -5,12 +5,12 @@
5
5
  import logging
6
6
  from collections import abc
7
7
  from pathlib import Path
8
- from typing import Union
8
+ from typing import Any, Iterator, List, Union
9
9
 
10
- from construct import Int16ub
10
+ from construct import Int16ub, Struct
11
11
 
12
12
  from . import structs
13
- from .tags import TAGS
13
+ from .tags import TAGS, AbstractAnlzTag, StructNotInitializedError
14
14
 
15
15
  logger = logging.getLogger(__name__)
16
16
 
@@ -18,35 +18,35 @@ XOR_MASK = bytearray.fromhex("CB E1 EE FA E5 EE AD EE E9 D2 E9 EB E1 E9 F3 E8 E9
18
18
 
19
19
 
20
20
  class BuildFileLengthError(Exception):
21
- def __init__(self, struct, len_data):
21
+ def __init__(self, struct: Struct, len_data: int) -> None:
22
22
  super().__init__(
23
23
  f"`len_file` ({struct.len_file}) of '{struct.type}' does not "
24
24
  f"match the data-length ({len_data})!"
25
25
  )
26
26
 
27
27
 
28
- class AnlzFile(abc.Mapping):
28
+ class AnlzFile(abc.Mapping): # type: ignore[type-arg]
29
29
  """Rekordbox `ANLZnnnn.xxx` binary file handler."""
30
30
 
31
- def __init__(self):
32
- self._path = ""
33
- self.file_header = None
34
- self.tags = list()
31
+ def __init__(self) -> None:
32
+ self._path: str = ""
33
+ self.file_header: Union[Struct, None] = None
34
+ self.tags: List[AbstractAnlzTag] = list()
35
35
 
36
36
  @property
37
- def num_tags(self):
37
+ def num_tags(self) -> int:
38
38
  return len(self.tags)
39
39
 
40
40
  @property
41
- def tag_types(self):
41
+ def tag_types(self) -> List[str]:
42
42
  return [tag.type for tag in self.tags]
43
43
 
44
44
  @property
45
- def path(self):
45
+ def path(self) -> str:
46
46
  return self._path
47
47
 
48
48
  @classmethod
49
- def parse(cls, data: bytes):
49
+ def parse(cls, data: bytes) -> "AnlzFile":
50
50
  """Parses the in-memory data of a Rekordbox analysis binary file.
51
51
 
52
52
  Parameters
@@ -64,7 +64,7 @@ class AnlzFile(abc.Mapping):
64
64
  return self
65
65
 
66
66
  @classmethod
67
- def parse_file(cls, path: Union[str, Path]):
67
+ def parse_file(cls, path: Union[str, Path]) -> "AnlzFile":
68
68
  """Reads and parses a Rekordbox analysis binary file.
69
69
 
70
70
  Parameters
@@ -92,10 +92,10 @@ class AnlzFile(abc.Mapping):
92
92
  data = fh.read()
93
93
 
94
94
  self = cls.parse(data)
95
- self._path = path
95
+ self._path = str(path)
96
96
  return self
97
97
 
98
- def _parse(self, data: bytes):
98
+ def _parse(self, data: bytes) -> None:
99
99
  file_header = structs.AnlzFileHeader.parse(data)
100
100
  tag_type = file_header.type
101
101
  assert tag_type == "PMAI"
@@ -136,6 +136,8 @@ class AnlzFile(abc.Mapping):
136
136
  try:
137
137
  # Parse the struct
138
138
  tag = TAGS[tag_type](tag_data)
139
+ if tag.struct is None:
140
+ raise StructNotInitializedError()
139
141
  tags.append(tag)
140
142
  len_header = tag.struct.len_header
141
143
  len_tag = tag.struct.len_tag
@@ -147,28 +149,34 @@ class AnlzFile(abc.Mapping):
147
149
  )
148
150
  except KeyError:
149
151
  logger.warning("Tag '%s' not supported!", tag_type)
150
- tag = structs.AnlzTag.parse(tag_data)
151
- len_tag = tag.len_tag
152
+ tag_struct = structs.AnlzTag.parse(tag_data)
153
+ len_tag = tag_struct.len_tag
152
154
  i += len_tag
153
155
 
154
156
  self.file_header = file_header
155
157
  self.tags = tags
156
158
 
157
- def update_len(self):
159
+ def update_len(self) -> None:
158
160
  # Update struct lengths
161
+ if self.file_header is None:
162
+ raise StructNotInitializedError()
159
163
  tags_len = 0
160
164
  for tag in self.tags:
165
+ if tag.struct is None:
166
+ raise StructNotInitializedError()
161
167
  tag.update_len()
162
168
  tags_len += tag.struct.len_tag
163
169
  # Update file length
164
170
  len_file = self.file_header.len_header + tags_len
165
171
  self.file_header.len_file = len_file
166
172
 
167
- def build(self):
173
+ def build(self) -> bytes:
174
+ if self.file_header is None:
175
+ raise StructNotInitializedError()
168
176
  self.update_len()
169
177
  header_data = structs.AnlzFileHeader.build(self.file_header)
170
178
  section_data = b"".join(tag.build() for tag in self.tags)
171
- data = header_data + section_data
179
+ data: bytes = header_data + section_data
172
180
  # Check `len_file`
173
181
  len_file = self.file_header.len_file
174
182
  len_data = len(data)
@@ -177,38 +185,38 @@ class AnlzFile(abc.Mapping):
177
185
 
178
186
  return data
179
187
 
180
- def save(self, path=""):
188
+ def save(self, path: Union[str, Path] = "") -> None:
181
189
  path = path or self._path
182
190
 
183
191
  data = self.build()
184
192
  with open(path, "wb") as fh:
185
193
  fh.write(data)
186
194
 
187
- def get_tag(self, key):
195
+ def get_tag(self, key: str) -> AbstractAnlzTag:
188
196
  return self.__getitem__(key)[0]
189
197
 
190
- def getall_tags(self, key):
198
+ def getall_tags(self, key: str) -> List[AbstractAnlzTag]:
191
199
  return self.__getitem__(key)
192
200
 
193
- def get(self, key):
201
+ def get(self, key: str) -> Any: # type: ignore[override]
194
202
  return self.__getitem__(key)[0].get()
195
203
 
196
- def getall(self, key):
204
+ def getall(self, key: str) -> List[Any]:
197
205
  return [tag.get() for tag in self.__getitem__(key)]
198
206
 
199
- def __len__(self):
207
+ def __len__(self) -> int:
200
208
  return len(self.keys())
201
209
 
202
- def __iter__(self):
210
+ def __iter__(self) -> Iterator[str]:
203
211
  return iter(set(tag.type for tag in self.tags))
204
212
 
205
- def __getitem__(self, item):
213
+ def __getitem__(self, item: str) -> List[AbstractAnlzTag]:
206
214
  if item.isupper() and len(item) == 4:
207
215
  return [tag for tag in self.tags if tag.type == item]
208
216
  else:
209
217
  return [tag for tag in self.tags if tag.name == item]
210
218
 
211
- def __contains__(self, item):
219
+ def __contains__(self, item: str) -> bool: # type: ignore[override]
212
220
  if item.isupper() and len(item) == 4:
213
221
  for tag in self.tags:
214
222
  if item == tag.type:
@@ -219,9 +227,9 @@ class AnlzFile(abc.Mapping):
219
227
  return True
220
228
  return False
221
229
 
222
- def __repr__(self):
230
+ def __repr__(self) -> str:
223
231
  return f"{self.__class__.__name__}({self.tag_types})"
224
232
 
225
- def set_path(self, path):
233
+ def set_path(self, path: Union[Path, str]) -> None:
226
234
  tag = self.get_tag("PPTH")
227
235
  tag.set(path)
pyrekordbox/anlz/tags.py CHANGED
@@ -4,16 +4,28 @@
4
4
 
5
5
  import logging
6
6
  from abc import ABC
7
+ from pathlib import Path
8
+ from typing import Any, Sequence, Tuple, Union
7
9
 
8
10
  import numpy as np
11
+ import numpy.typing as npt
12
+ from construct import Struct
13
+ from construct.lib.containers import Container
9
14
 
10
15
  from . import structs
11
16
 
12
17
  logger = logging.getLogger(__name__)
13
18
 
14
19
 
20
+ class StructNotInitializedError(Exception):
21
+ """Raised when a struct is not initialized."""
22
+
23
+ def __init__(self) -> None:
24
+ super().__init__("Struct is not initialized!")
25
+
26
+
15
27
  class BuildTagLengthError(Exception):
16
- def __init__(self, struct, len_data):
28
+ def __init__(self, struct: Struct, len_data: int) -> None:
17
29
  super().__init__(
18
30
  f"`len_tag` ({struct.len_tag}) of '{struct.type}' does not "
19
31
  f"match the data-length ({len_data})!"
@@ -28,16 +40,20 @@ class AbstractAnlzTag(ABC):
28
40
  LEN_HEADER: int = 0 # Expected value of `len_header`
29
41
  LEN_TAG: int = 0 # Expected value of `len_tag`
30
42
 
31
- def __init__(self, tag_data):
32
- self.struct = None
43
+ def __init__(self, tag_data: bytes) -> None:
44
+ self.struct: Union[Struct, None] = None
33
45
  if tag_data is not None:
34
46
  self.parse(tag_data)
35
47
 
36
48
  @property
37
- def content(self):
49
+ def content(self) -> Container:
50
+ if self.struct is None:
51
+ raise StructNotInitializedError()
38
52
  return self.struct.content
39
53
 
40
- def _check_len_header(self):
54
+ def _check_len_header(self) -> None:
55
+ if self.struct is None:
56
+ raise StructNotInitializedError()
41
57
  if self.LEN_HEADER != self.struct.len_header:
42
58
  logger.warning(
43
59
  "`len_header` (%s) of `%s` doesn't match the expected value %s",
@@ -46,7 +62,9 @@ class AbstractAnlzTag(ABC):
46
62
  self.LEN_HEADER,
47
63
  )
48
64
 
49
- def _check_len_tag(self):
65
+ def _check_len_tag(self) -> None:
66
+ if self.struct is None:
67
+ raise StructNotInitializedError()
50
68
  if self.LEN_TAG != self.struct.len_tag:
51
69
  logger.warning(
52
70
  "`len_tag` (%s) of `%s` doesn't match the expected value %s",
@@ -55,10 +73,10 @@ class AbstractAnlzTag(ABC):
55
73
  self.LEN_TAG,
56
74
  )
57
75
 
58
- def check_parse(self):
76
+ def check_parse(self) -> None:
59
77
  pass
60
78
 
61
- def parse(self, tag_data):
79
+ def parse(self, tag_data: bytes) -> None:
62
80
  self.struct = structs.AnlzTag.parse(tag_data)
63
81
  if self.LEN_HEADER:
64
82
  self._check_len_header()
@@ -66,32 +84,40 @@ class AbstractAnlzTag(ABC):
66
84
  self._check_len_tag()
67
85
  self.check_parse()
68
86
 
69
- def build(self):
70
- data = structs.AnlzTag.build(self.struct)
87
+ def build(self) -> bytes:
88
+ if self.struct is None:
89
+ raise StructNotInitializedError()
90
+ data: bytes = structs.AnlzTag.build(self.struct)
71
91
  len_data = len(data)
72
92
  if len_data != self.struct.len_tag:
73
93
  raise BuildTagLengthError(self.struct, len_data)
74
94
  return data
75
95
 
76
- def get(self):
96
+ def get(self) -> Container:
97
+ if self.struct is None:
98
+ raise StructNotInitializedError()
77
99
  return self.struct.content
78
100
 
79
- def set(self, *args, **kwargs):
101
+ def set(self, *args: Any, **kwargs: Any) -> None:
80
102
  pass
81
103
 
82
- def update_len(self):
104
+ def update_len(self) -> None:
83
105
  pass
84
106
 
85
- def __repr__(self):
107
+ def __repr__(self) -> str:
108
+ if self.struct is None:
109
+ raise StructNotInitializedError()
86
110
  len_header = self.struct.len_header
87
111
  len_tag = self.struct.len_tag
88
112
  return f"{self.__class__.__name__}(len_header={len_header}, len_tag={len_tag})"
89
113
 
90
- def pformat(self):
114
+ def pformat(self) -> str:
115
+ if self.struct is None:
116
+ raise StructNotInitializedError()
91
117
  return str(self.struct)
92
118
 
93
119
 
94
- def _parse_wf_preview(tag):
120
+ def _parse_wf_preview(tag: structs.AnlzTag) -> Tuple[npt.NDArray[np.int8], npt.NDArray[np.int8]]:
95
121
  n = len(tag.entries)
96
122
  wf = np.zeros(n, dtype=np.int8)
97
123
  col = np.zeros(n, dtype=np.int8)
@@ -110,32 +136,32 @@ class PQTZAnlzTag(AbstractAnlzTag):
110
136
  LEN_HEADER = 24
111
137
 
112
138
  @property
113
- def count(self):
139
+ def count(self) -> int:
114
140
  return len(self.content.entries)
115
141
 
116
142
  @property
117
- def beats(self):
143
+ def beats(self) -> npt.NDArray[np.int8]:
118
144
  return self.get_beats()
119
145
 
120
146
  @property
121
- def bpms(self):
147
+ def bpms(self) -> npt.NDArray[np.float64]:
122
148
  return self.get_bpms()
123
149
 
124
150
  @property
125
- def bpms_average(self):
151
+ def bpms_average(self) -> float:
126
152
  if len(self.content.entries):
127
- return np.mean(self.get_bpms())
153
+ return float(np.mean(self.get_bpms()))
128
154
  return 0.0
129
155
 
130
156
  @property
131
- def bpms_unique(self):
157
+ def bpms_unique(self) -> Any:
132
158
  return np.unique(self.get_bpms())
133
159
 
134
160
  @property
135
- def times(self):
161
+ def times(self) -> npt.NDArray[np.float64]:
136
162
  return self.get_times()
137
163
 
138
- def get(self):
164
+ def get(self) -> Tuple[npt.NDArray[np.int8], npt.NDArray[np.float64], npt.NDArray[np.float64]]:
139
165
  n = len(self.content.entries)
140
166
  beats = np.zeros(n, dtype=np.int8)
141
167
  bpms = np.zeros(n, dtype=np.float64)
@@ -147,16 +173,16 @@ class PQTZAnlzTag(AbstractAnlzTag):
147
173
  times[i] = time / 1_000 # Convert milliseconds to seconds
148
174
  return beats, bpms, times
149
175
 
150
- def get_beats(self):
176
+ def get_beats(self) -> npt.NDArray[np.int8]:
151
177
  return np.array([entry.beat for entry in self.content.entries], dtype=np.int8)
152
178
 
153
- def get_bpms(self):
154
- return np.array([entry.tempo / 100 for entry in self.content.entries])
179
+ def get_bpms(self) -> npt.NDArray[np.float64]:
180
+ return np.array([entry.tempo / 100 for entry in self.content.entries], dtype=np.float64)
155
181
 
156
- def get_times(self):
157
- return np.array([entry.time / 1000 for entry in self.content.entries])
182
+ def get_times(self) -> npt.NDArray[np.float64]:
183
+ return np.array([entry.time / 1000 for entry in self.content.entries], dtype=np.float64)
158
184
 
159
- def set(self, beats, bpms, times):
185
+ def set(self, beats: Sequence[int], bpms: Sequence[float], times: Sequence[float]) -> None:
160
186
  n = len(self.content.entries)
161
187
  n_beats = len(beats)
162
188
  n_bpms = len(bpms)
@@ -176,7 +202,7 @@ class PQTZAnlzTag(AbstractAnlzTag):
176
202
  data = {"beat": int(beat), "tempo": int(100 * bpm), "time": int(1000 * t)}
177
203
  self.content.entries[i].update(data)
178
204
 
179
- def set_beats(self, beats):
205
+ def set_beats(self, beats: Sequence[int]) -> None:
180
206
  n = len(self.content.entries)
181
207
  n_new = len(beats)
182
208
  if n_new != n:
@@ -185,7 +211,7 @@ class PQTZAnlzTag(AbstractAnlzTag):
185
211
  for i, beat in enumerate(beats):
186
212
  self.content.entries[i].beat = beat
187
213
 
188
- def set_bpms(self, bpms):
214
+ def set_bpms(self, bpms: Sequence[float]) -> None:
189
215
  n = len(self.content.entries)
190
216
  n_new = len(bpms)
191
217
  if n_new != n:
@@ -194,7 +220,7 @@ class PQTZAnlzTag(AbstractAnlzTag):
194
220
  for i, bpm in enumerate(bpms):
195
221
  self.content.entries[i].tempo = int(bpm * 100)
196
222
 
197
- def set_times(self, times):
223
+ def set_times(self, times: Sequence[float]) -> None:
198
224
  n = len(self.content.entries)
199
225
  n_new = len(times)
200
226
  if n_new != n:
@@ -203,10 +229,14 @@ class PQTZAnlzTag(AbstractAnlzTag):
203
229
  for i, t in enumerate(times):
204
230
  self.content.entries[i].time = int(1000 * t)
205
231
 
206
- def check_parse(self):
232
+ def check_parse(self) -> None:
233
+ if self.struct is None:
234
+ raise StructNotInitializedError()
207
235
  assert self.struct.content.entry_count == len(self.struct.content.entries)
208
236
 
209
- def update_len(self):
237
+ def update_len(self) -> None:
238
+ if self.struct is None:
239
+ raise StructNotInitializedError()
210
240
  self.struct.len_tag = self.struct.len_header + 8 * len(self.content.entries)
211
241
 
212
242
 
@@ -220,33 +250,36 @@ class PQT2AnlzTag(AbstractAnlzTag):
220
250
  count = 2
221
251
 
222
252
  @property
223
- def beats(self):
253
+ def beats(self) -> npt.NDArray[np.int8]:
224
254
  return self.get_beats()
225
255
 
226
256
  @property
227
- def bpms(self):
257
+ def bpms(self) -> npt.NDArray[np.float64]:
228
258
  return self.get_bpms()
229
259
 
230
260
  @property
231
- def times(self):
261
+ def times(self) -> npt.NDArray[np.float64]:
232
262
  return self.get_times()
233
263
 
234
264
  @property
235
- def beat_grid_count(self):
236
- return self.content.entry_count
265
+ def beat_grid_count(self) -> int:
266
+ count: int = self.content.entry_count
267
+ return count
237
268
 
238
269
  @property
239
- def bpms_unique(self):
270
+ def bpms_unique(self) -> Any:
240
271
  return np.unique(self.get_bpms())
241
272
 
242
- def check_parse(self):
273
+ def check_parse(self) -> None:
274
+ if self.struct is None:
275
+ raise StructNotInitializedError()
243
276
  len_beats = self.struct.content.entry_count
244
277
  if len_beats:
245
278
  expected = self.struct.len_tag - self.struct.len_header
246
279
  actual = 2 * len(self.content.entries) # each entry consist of 2 bytes
247
280
  assert actual == expected, f"{actual} != {expected}"
248
281
 
249
- def get(self):
282
+ def get(self) -> Tuple[npt.NDArray[np.int8], npt.NDArray[np.float64], npt.NDArray[np.float64]]:
250
283
  n = len(self.content.bpm)
251
284
  beats = np.zeros(n, dtype=np.int8)
252
285
  bpms = np.zeros(n, dtype=np.float64)
@@ -258,32 +291,34 @@ class PQT2AnlzTag(AbstractAnlzTag):
258
291
  times[i] = time / 1_000 # Convert milliseconds to seconds
259
292
  return beats, bpms, times
260
293
 
261
- def get_beats(self):
294
+ def get_beats(self) -> npt.NDArray[np.int8]:
262
295
  return np.array([entry.beat for entry in self.content.bpm], dtype=np.int8)
263
296
 
264
- def get_bpms(self):
265
- return np.array([entry.tempo / 100 for entry in self.content.bpm])
297
+ def get_bpms(self) -> npt.NDArray[np.float64]:
298
+ return np.array([entry.tempo / 100 for entry in self.content.bpm], dtype=np.float64)
266
299
 
267
- def get_times(self):
268
- return np.array([entry.time / 1000 for entry in self.content.bpm])
300
+ def get_times(self) -> npt.NDArray[np.float64]:
301
+ return np.array([entry.time / 1000 for entry in self.content.bpm], dtype=np.float64)
269
302
 
270
- def get_beat_grid(self):
303
+ def get_beat_grid(self) -> npt.NDArray[np.int8]:
271
304
  return np.array([entry.beat for entry in self.content.entries], dtype=np.int8)
272
305
 
273
- def set_beats(self, beats):
306
+ def set_beats(self, beats: Sequence[int]) -> None:
274
307
  for i, beat in enumerate(beats):
275
308
  self.content.bpm[i].beat = beat
276
309
 
277
- def set_bpms(self, bpms):
310
+ def set_bpms(self, bpms: Sequence[float]) -> None:
278
311
  for i, bpm in enumerate(bpms):
279
312
  self.content.bpm[i].bpm = int(bpm * 100)
280
313
 
281
- def set_times(self, times):
314
+ def set_times(self, times: Sequence[float]) -> None:
282
315
  for i, t in enumerate(times):
283
316
  self.content.bpm[i].time = int(1000 * t)
284
317
 
285
- def build(self):
286
- data = structs.AnlzTag.build(self.struct)
318
+ def build(self) -> bytes:
319
+ if self.struct is None:
320
+ raise StructNotInitializedError()
321
+ data: bytes = structs.AnlzTag.build(self.struct)
287
322
  if self.struct.content.entry_count == 0:
288
323
  data = data[: self.struct.len_tag]
289
324
 
@@ -317,19 +352,22 @@ class PPTHAnlzTag(AbstractAnlzTag):
317
352
  LEN_HEADER = 16
318
353
 
319
354
  @property
320
- def path(self):
321
- return self.content.path
355
+ def path(self) -> str:
356
+ path: str = self.content.path
357
+ return path
322
358
 
323
- def get(self):
324
- return self.content.path
359
+ def get(self) -> str:
360
+ return self.path
325
361
 
326
- def set(self, path):
327
- path = path.replace("\\", "/")
328
- len_path = len(path.encode("utf-16-be")) + 2
329
- self.content.path = path
362
+ def set(self, path: Union[str, Path]) -> None:
363
+ pathstr = str(path).replace("\\", "/")
364
+ len_path = len(pathstr.encode("utf-16-be")) + 2
365
+ self.content.path = pathstr
330
366
  self.content.len_path = len_path
331
367
 
332
- def update_len(self):
368
+ def update_len(self) -> None:
369
+ if self.struct is None:
370
+ raise StructNotInitializedError()
333
371
  self.struct.len_tag = self.struct.len_header + self.content.len_path
334
372
 
335
373
 
@@ -341,8 +379,8 @@ class PVBRAnlzTag(AbstractAnlzTag):
341
379
  LEN_HEADER = 16
342
380
  LEN_TAG = 1620
343
381
 
344
- def get(self):
345
- return np.array(self.content.idx)
382
+ def get(self) -> npt.NDArray[np.uint64]:
383
+ return np.array(self.content.idx, dtype=np.uint64)
346
384
 
347
385
 
348
386
  class PSSIAnlzTag(AbstractAnlzTag):
@@ -360,7 +398,7 @@ class PWAVAnlzTag(AbstractAnlzTag):
360
398
  name = "wf_preview"
361
399
  LEN_HEADER = 20
362
400
 
363
- def get(self):
401
+ def get(self) -> Tuple[npt.NDArray[np.int8], npt.NDArray[np.int8]]:
364
402
  return _parse_wf_preview(self.content)
365
403
 
366
404
 
@@ -371,7 +409,7 @@ class PWV2AnlzTag(AbstractAnlzTag):
371
409
  name = "wf_tiny_preview"
372
410
  LEN_HEADER = 20
373
411
 
374
- def get(self):
412
+ def get(self) -> Tuple[npt.NDArray[np.int8], npt.NDArray[np.int8]]:
375
413
  return _parse_wf_preview(self.content)
376
414
 
377
415
 
@@ -382,7 +420,7 @@ class PWV3AnlzTag(AbstractAnlzTag):
382
420
  name = "wf_detail"
383
421
  LEN_HEADER = 24
384
422
 
385
- def get(self):
423
+ def get(self) -> Tuple[npt.NDArray[np.int8], npt.NDArray[np.int8]]:
386
424
  return _parse_wf_preview(self.content)
387
425
 
388
426
 
@@ -393,7 +431,7 @@ class PWV4AnlzTag(AbstractAnlzTag):
393
431
  name = "wf_color"
394
432
  LEN_HEADER = 24
395
433
 
396
- def get(self):
434
+ def get(self) -> Tuple[npt.NDArray[np.int64], npt.NDArray[np.int64], npt.NDArray[np.int64]]:
397
435
  num_entries = self.content.len_entries
398
436
  data = self.content.entries
399
437
  ws, hs = 1, 1
@@ -430,7 +468,7 @@ class PWV5AnlzTag(AbstractAnlzTag):
430
468
  name = "wf_color_detail"
431
469
  LEN_HEADER = 24
432
470
 
433
- def get(self):
471
+ def get(self) -> Tuple[npt.NDArray[np.float64], npt.NDArray[np.int64]]:
434
472
  """Parse the Waveform Color Detail Tag (PWV5).
435
473
 
436
474
  The format of the entries is: