PyCriCodecsEx 0.0.3__cp312-cp312-win32.whl → 0.0.5__cp312-cp312-win32.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.
Binary file
PyCriCodecsEx/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.0.3"
1
+ __version__ = "0.0.5"
PyCriCodecsEx/acb.py CHANGED
@@ -14,12 +14,16 @@ from copy import deepcopy
14
14
 
15
15
  class CueNameTable(UTFViewer):
16
16
  CueIndex: int
17
+ '''Index into CueTable'''
17
18
  CueName: str
19
+ '''Name of the cue'''
18
20
 
19
21
 
20
22
  class CueTable(UTFViewer):
21
23
  CueId: int
24
+ '''Corresponds to the cue index found in CueNameTable'''
22
25
  Length: int
26
+ '''Duration of the cue in milliseconds'''
23
27
  ReferenceIndex: int
24
28
  ReferenceType: int
25
29
 
@@ -52,14 +56,21 @@ class WaveformTable(UTFViewer):
52
56
 
53
57
 
54
58
  class ACBTable(UTFViewer):
59
+ '''ACB Table View'''
60
+
55
61
  AcbGuid: bytes
62
+ '''GUID of the ACB. This SHOULD be different for each ACB file.'''
56
63
  Name: str
57
- Version: int
64
+ '''Name of the ACB. This is usually the name of the sound bank.'''
65
+ Version: int
58
66
  VersionString: str
59
67
 
60
68
  AwbFile: bytes
61
69
  CueNameTable: List[CueNameTable]
70
+ '''A list of cue names with their corresponding indices into CueTable'''
62
71
  CueTable: List[CueTable]
72
+ '''A list of cues with their corresponding references'''
73
+
63
74
  SequenceTable: List[SequenceTable]
64
75
  SynthTable: List[SynthTable]
65
76
  TrackEventTable: List[TrackEventTable]
@@ -67,7 +78,7 @@ class ACBTable(UTFViewer):
67
78
  WaveformTable: List[WaveformTable]
68
79
 
69
80
  @staticmethod
70
- def decode_tlv(data : bytes):
81
+ def _decode_tlv(data : bytes):
71
82
  pos = 0
72
83
  while pos < len(data):
73
84
  tag = data[pos : pos + 2]
@@ -76,16 +87,16 @@ class ACBTable(UTFViewer):
76
87
  pos += 3 + length
77
88
  yield (tag, value)
78
89
 
79
- def waveform_of_track(self, index: int):
80
- tlv = self.decode_tlv(self.TrackEventTable[index])
90
+ def _waveform_of_track(self, index: int):
91
+ tlv = self._decode_tlv(self.TrackEventTable[index])
81
92
  def noteOn(data: bytes):
82
93
  # Handle note on event
83
94
  tlv_type, tlv_index = AcbTrackCommandNoteOnStruct.unpack(data[:AcbTrackCommandNoteOnStruct.size])
84
95
  match tlv_type:
85
96
  case 0x02: # Synth
86
- yield from self.waveform_of_synth(tlv_index)
97
+ yield from self._waveform_of_synth(tlv_index)
87
98
  case 0x03: # Sequence
88
- yield from self.waveform_of_sequence(tlv_index)
99
+ yield from self._waveform_of_sequence(tlv_index)
89
100
  # Ignore others silently
90
101
  for code, data in tlv:
91
102
  match code:
@@ -94,13 +105,13 @@ class ACBTable(UTFViewer):
94
105
  case 2003:
95
106
  yield from noteOn(data)
96
107
 
97
- def waveform_of_sequence(self, index : int):
108
+ def _waveform_of_sequence(self, index : int):
98
109
  seq = self.SequenceTable[index]
99
110
  for i in range(seq.NumTracks):
100
111
  track_index = int.from_bytes(seq.TrackIndex[i*2:i*2+2], 'big')
101
112
  yield self.WaveformTable[track_index]
102
113
 
103
- def waveform_of_synth(self, index: int):
114
+ def _waveform_of_synth(self, index: int):
104
115
  item_type, item_index = AcbSynthReferenceStruct.unpack(self.SynthTable[index].ReferenceItems)
105
116
  match item_type:
106
117
  case 0x00: # No audio
@@ -108,33 +119,42 @@ class ACBTable(UTFViewer):
108
119
  case 0x01: # Waveform
109
120
  yield self.WaveformTable[item_index]
110
121
  case 0x02: # Yet another synth...
111
- yield from self.waveform_of_synth(item_index)
122
+ yield from self._waveform_of_synth(item_index)
112
123
  case 0x03: # Sequence
113
- yield from self.waveform_of_sequence(item_index)
124
+ yield from self._waveform_of_sequence(item_index)
114
125
  case _:
115
126
  raise NotImplementedError(f"Unknown synth reference type: {item_type} at index {index}")
116
127
 
117
128
  def waveform_of(self, index : int) -> List["WaveformTable"]:
129
+ """Retrieves the waveform(s) associated with a cue.
130
+
131
+ Cues may reference multiple waveforms, which could also be reused."""
118
132
  cue = next(filter(lambda c: c.CueId == index, self.CueTable), None)
119
133
  assert cue, "cue of index %d not found" % index
120
134
  match cue.ReferenceType:
121
135
  case 0x01:
122
136
  return [self.WaveformTable[index]]
123
137
  case 0x02:
124
- return list(self.waveform_of_synth(index))
138
+ return list(self._waveform_of_synth(index))
125
139
  case 0x03:
126
- return list(self.waveform_of_sequence(index))
140
+ return list(self._waveform_of_sequence(index))
127
141
  case 0x08:
128
142
  raise NotImplementedError("BlockSequence type not implemented yet")
129
143
  case _:
130
144
  raise NotImplementedError(f"Unknown cue reference type: {cue.ReferenceType}")
131
145
 
132
146
  @dataclass(frozen=True)
133
- class CueItem:
147
+ class PackedCueItem:
148
+ '''Helper class for read-only cue information'''
149
+
134
150
  CueId: int
151
+ '''Cue ID'''
135
152
  CueName: str
153
+ '''Cue name'''
136
154
  Length: float
137
- Waveforms: list[int] # List of waveform IDs
155
+ '''Duration in seconds'''
156
+ Waveforms: list[int]
157
+ '''List of waveform IDs, corresponds to ACB.get_waveforms()'''
138
158
 
139
159
  class ACB(UTF):
140
160
  """Use this class to read, and modify ACB files in memory."""
@@ -168,10 +188,17 @@ class ACB(UTF):
168
188
  """Returns the AWB object associated with the ACB."""
169
189
  return AWB(self.view.AwbFile)
170
190
 
171
- def get_waveforms(self) -> List[HCACodec | ADXCodec | Tuple[AcbEncodeTypes, int, int, int, bytes]]:
191
+ def get_waveforms(self, **kwargs) -> List[HCACodec | ADXCodec | Tuple[AcbEncodeTypes, int, int, int, bytes]]:
172
192
  """Returns a list of decoded waveforms.
173
193
 
174
194
  Item may be a codec (if known), or a tuple of (Codec ID, Channel Count, Sample Count, Sample Rate, Raw data).
195
+
196
+ Additional keyword arguments are passed to the codec constructors. e.g. for encrypted HCA payloads,
197
+ you may do the following:
198
+ ```python
199
+ get_waveforms(key=..., subkey=...)
200
+ ```
201
+ See also the respective docs (ADXCodec, HCACodec) for more details.
175
202
  """
176
203
  CODEC_TABLE = {
177
204
  AcbEncodeTypes.ADX: ADXCodec,
@@ -184,7 +211,7 @@ class ACB(UTF):
184
211
  encode = AcbEncodeTypes(wav.EncodeType)
185
212
  codec = (CODEC_TABLE.get(encode, None))
186
213
  if codec:
187
- wavs.append(codec(awb.get_file_at(wav.MemoryAwbId)))
214
+ wavs.append(codec(awb.get_file_at(wav.MemoryAwbId), **kwargs))
188
215
  else:
189
216
  wavs.append((encode, wav.NumChannels, wav.NumSamples, wav.SamplingRate, awb.get_file_at(wav.MemoryAwbId)))
190
217
  return wavs
@@ -194,7 +221,7 @@ class ACB(UTF):
194
221
 
195
222
  Input item may be a codec (if known), or a tuple of (Codec ID, Channel Count, Sample Count, Sample Rate, Raw data).
196
223
 
197
- NOTE: Cue duration is not set. You need to change that manually.
224
+ NOTE: Cue duration is not set. You need to change that manually - this is usually unecessary as the player will just play until the end of the waveform.
198
225
  """
199
226
  WAVEFORM = self.view.WaveformTable[0]._payload.copy()
200
227
  encoded = []
@@ -234,7 +261,7 @@ class ACB(UTF):
234
261
  pass
235
262
 
236
263
  @property
237
- def cues(self) -> Generator[CueItem, None, None]:
264
+ def cues(self) -> Generator[PackedCueItem, None, None]:
238
265
  """Returns a generator of **read-only** Cues.
239
266
 
240
267
  Cues reference waveform bytes by their AWB IDs, which can be accessed via `waveforms`.
@@ -242,7 +269,7 @@ class ACB(UTF):
242
269
  """
243
270
  for name, cue in zip(self.view.CueNameTable, self.view.CueTable):
244
271
  waveforms = self.view.waveform_of(cue.CueId)
245
- yield CueItem(cue.CueId, name.CueName, cue.Length / 1000.0, [waveform.MemoryAwbId for waveform in waveforms])
272
+ yield PackedCueItem(cue.CueId, name.CueName, cue.Length / 1000.0, [waveform.MemoryAwbId for waveform in waveforms])
246
273
 
247
274
  class ACBBuilder:
248
275
  """Use this class to build ACB files from an existing ACB object."""
PyCriCodecsEx/adx.py CHANGED
@@ -57,7 +57,7 @@ class ADXCodec(ADX):
57
57
 
58
58
  Args:
59
59
  stream (str | bytes): Path to the ADX or WAV file, or a BinaryIO stream. WAV files will be automatically encoded with the given settings first.
60
- filename (str, optional): Output filename. Defaults to "default.adx".
60
+ filename (str, optional): Filename, used by USMBuilder. Defaults to "default.adx".
61
61
  bitdepth (int, optional): Audio bit depth within [2,15]. Defaults to 4.
62
62
  """
63
63
  if type(stream) == str:
@@ -144,12 +144,15 @@ class ADXCodec(ADX):
144
144
  def get_metadata(self):
145
145
  return None
146
146
 
147
- def get_encoded(self):
147
+ def get_encoded(self) -> bytes:
148
148
  """Gets the encoded ADX audio data."""
149
149
  return self.adx
150
150
 
151
- def save(self, filepath: str):
152
- """Saves the encoded ADX audio to filepath"""
153
- with open(filepath, "wb") as f:
154
- f.write(self.decode(self.adx))
151
+ def save(self, filepath: str | BinaryIO):
152
+ """Saves the decoded WAV audio to filepath or a writable stream"""
153
+ if type(filepath) == str:
154
+ with open(filepath, "wb") as f:
155
+ f.write(self.decode(self.adx))
156
+ else:
157
+ filepath.write(self.decode(self.adx))
155
158
 
PyCriCodecsEx/awb.py CHANGED
@@ -1,6 +1,5 @@
1
1
  from io import BytesIO, FileIO
2
- import os
3
- from typing import BinaryIO
2
+ from typing import BinaryIO, Generator
4
3
  from struct import iter_unpack, pack
5
4
  from PyCriCodecsEx.chunk import *
6
5
  from PyCriCodecsEx.hca import HCA
@@ -55,7 +54,7 @@ class AWB:
55
54
  self.headersize = self.headersize + (self.align - (self.headersize % self.align))
56
55
  self.stream.seek(self.headersize, 0)
57
56
 
58
- def get_files(self):
57
+ def get_files(self) -> Generator[bytes, None, None]:
59
58
  """Generator function to yield all data blobs from an AWB. """
60
59
  self.stream.seek(self.headersize, 0)
61
60
  for i in range(1, len(self.ofs)):
PyCriCodecsEx/cpk.py CHANGED
@@ -1,20 +1,27 @@
1
1
  import os
2
- from typing import BinaryIO
2
+ from typing import BinaryIO, Generator
3
3
  from io import BytesIO, FileIO
4
4
  from PyCriCodecsEx.chunk import *
5
5
  from PyCriCodecsEx.utf import UTF, UTFBuilder
6
6
  from dataclasses import dataclass
7
- from concurrent.futures import ProcessPoolExecutor, as_completed
7
+ from concurrent.futures import ThreadPoolExecutor, as_completed
8
8
  from tempfile import NamedTemporaryFile
9
9
  import CriCodecsEx
10
10
 
11
- def _worker_do_compression(src : str, dst: str):
11
+ def _crilayla_compress_to_file(src : str, dst: str):
12
12
  with open(src, "rb") as fsrc, open(dst, "wb") as fdst:
13
13
  data = fsrc.read()
14
- compressed = CriCodecsEx.CriLaylaCompress(data)
15
- fdst.write(compressed)
14
+ try:
15
+ compressed = CriCodecsEx.CriLaylaCompress(data)
16
+ fdst.write(compressed)
17
+ except:
18
+ # Fallback for failed compression
19
+ # Again. FIXME.
20
+ fdst.write(data)
21
+
16
22
  @dataclass
17
- class _PackFile():
23
+ class PackedFile():
24
+ """Helper class for packed files within a CPK."""
18
25
  stream: BinaryIO
19
26
  path: str
20
27
  offset: int
@@ -22,6 +29,7 @@ class _PackFile():
22
29
  compressed : bool = False
23
30
 
24
31
  def get_bytes(self) -> bytes:
32
+ """Get the raw bytes of the packed file, decompressing if necessary."""
25
33
  self.stream.seek(self.offset)
26
34
  data = self.stream.read(self.size)
27
35
  if self.compressed:
@@ -29,6 +37,7 @@ class _PackFile():
29
37
  return data
30
38
 
31
39
  def save(self, path : str):
40
+ """Save the packed file to a specified path."""
32
41
  with open(path, "wb") as f:
33
42
  f.write(self.get_bytes())
34
43
  class _TOC():
@@ -115,6 +124,9 @@ class CPK:
115
124
 
116
125
  @property
117
126
  def mode(self):
127
+ """Get the current mode of the CPK archive. [0,1,2,3]
128
+
129
+ See also CPKBuilder"""
118
130
  TOC, ITOC, GTOC = 'TOC' in self.tables, 'ITOC' in self.tables, 'GTOC' in self.tables
119
131
  if TOC and ITOC and GTOC:
120
132
  return 3
@@ -127,8 +139,8 @@ class CPK:
127
139
  raise ValueError("Unknown CPK mode.")
128
140
 
129
141
  @property
130
- def files(self):
131
- """Retrieves a list of all files in the CPK archive."""
142
+ def files(self) -> Generator[PackedFile, None, None]:
143
+ """Creates a generator for all files in the CPK archive as PackedFile."""
132
144
  if "TOC" in self.tables:
133
145
  toctable = self.tables['TOC']
134
146
  rel_off = 0x800
@@ -139,10 +151,10 @@ class CPK:
139
151
  filename = filename[:250] + "_" + str(i) # 250 because i might be 4 digits long.
140
152
  if toctable['ExtractSize'][i] > toctable['FileSize'][i]:
141
153
  self.stream.seek(rel_off+toctable["FileOffset"][i], 0)
142
- yield _PackFile(self.stream, os.path.join(dirname,filename), self.stream.tell(), toctable['FileSize'][i], compressed=True)
154
+ yield PackedFile(self.stream, os.path.join(dirname,filename), self.stream.tell(), toctable['FileSize'][i], compressed=True)
143
155
  else:
144
156
  self.stream.seek(rel_off+toctable["FileOffset"][i], 0)
145
- yield _PackFile(self.stream, os.path.join(dirname,filename), self.stream.tell(), toctable['FileSize'][i])
157
+ yield PackedFile(self.stream, os.path.join(dirname,filename), self.stream.tell(), toctable['FileSize'][i])
146
158
  elif "ITOC" in self.tables:
147
159
  toctableL = self.tables["ITOC"]['DataL'][0]
148
160
  toctableH = self.tables["ITOC"]['DataH'][0]
@@ -154,18 +166,18 @@ class CPK:
154
166
  if i in toctableH['ID']:
155
167
  idx = toctableH['ID'].index(i)
156
168
  if toctableH['ExtractSize'][idx] > toctableH['FileSize'][idx]:
157
- yield _PackFile(self.stream, str(i), self.stream.tell(), toctableH['FileSize'][idx], compressed=True)
169
+ yield PackedFile(self.stream, str(i), self.stream.tell(), toctableH['FileSize'][idx], compressed=True)
158
170
  else:
159
- yield _PackFile(self.stream, str(i), self.stream.tell(), toctableH['FileSize'][idx])
171
+ yield PackedFile(self.stream, str(i), self.stream.tell(), toctableH['FileSize'][idx])
160
172
  if toctableH['FileSize'][idx] % align != 0:
161
173
  seek_size = (align - toctableH['FileSize'][idx] % align)
162
174
  self.stream.seek(seek_size, 1)
163
175
  elif i in toctableL['ID']:
164
176
  idx = toctableL['ID'].index(i)
165
177
  if toctableL['ExtractSize'][idx] > toctableL['FileSize'][idx]:
166
- yield _PackFile(self.stream, str(i), self.stream.tell(), toctableL['FileSize'][idx], compressed=True)
178
+ yield PackedFile(self.stream, str(i), self.stream.tell(), toctableL['FileSize'][idx], compressed=True)
167
179
  else:
168
- yield _PackFile(self.stream, str(i), self.stream.tell(), toctableL['FileSize'][idx])
180
+ yield PackedFile(self.stream, str(i), self.stream.tell(), toctableL['FileSize'][idx])
169
181
  if toctableL['FileSize'][idx] % align != 0:
170
182
  seek_size = (align - toctableL['FileSize'][idx] % align)
171
183
  self.stream.seek(seek_size, 1)
@@ -247,9 +259,8 @@ class CPKBuilder:
247
259
  compress (bool, optional): Whether to compress the file. Defaults to False.
248
260
 
249
261
  NOTE:
250
- - In ITOC-related mode, the insertion order determines the final integer ID of the files.
251
- - Compression can be VERY slow with high entropy files (e.g. encoded media). Use at discretion.
252
- """
262
+ - In ITOC-related mode, the insertion order determines the final integer ID of the files.
263
+ """
253
264
  if not dst and self.mode != 0:
254
265
  raise ValueError("Destination filename must be specified in non-ITOC mode.")
255
266
 
@@ -263,7 +274,7 @@ class CPKBuilder:
263
274
  self.outfile.write(bytes(0x800 - pack_size % 0x800))
264
275
  self.progress_cb("Write %s" % os.path.basename(filename), i + 1, len(self.files))
265
276
 
266
- def _populate_files(self, parallel : bool):
277
+ def _populate_files(self, threads : int = 1):
267
278
  self.files = []
268
279
  for src, dst, compress in self.in_files:
269
280
  if compress:
@@ -271,23 +282,15 @@ class CPKBuilder:
271
282
  self.os_files.append((tmp.name, True))
272
283
  else:
273
284
  self.os_files.append((src, False))
274
- if parallel:
275
- with ProcessPoolExecutor() as exec:
276
- futures = []
277
- for (src, _, _), (dst, compress) in zip(self.in_files,self.os_files):
278
- if compress:
279
- futures.append(exec.submit(_worker_do_compression, src, dst))
280
- for i, fut in as_completed(futures):
281
- try:
282
- fut.result()
283
- except:
284
- pass
285
- self.progress_cb("Compress %s" % os.path.basename(src), i + 1, len(futures))
286
- else:
287
- for i, ((src, _, _), (dst, compress)) in enumerate(zip(self.in_files,self.os_files)):
288
- if compress:
289
- _worker_do_compression(src, dst)
290
- self.progress_cb("Compress %s" % os.path.basename(src), i + 1, len(self.in_files))
285
+ with ThreadPoolExecutor(max_workers=threads) as exec:
286
+ futures = []
287
+ for (src, _, _), (dst, compress) in zip(self.in_files,self.os_files):
288
+ if compress:
289
+ _crilayla_compress_to_file(src, dst)
290
+ # futures.append(exec.submit(_crilayla_compress_to_file, src, dst))
291
+ for i, fut in enumerate(as_completed(futures)):
292
+ fut.result()
293
+ self.progress_cb("Compress %s" % os.path.basename(src), i + 1, len(futures))
291
294
  for (src, filename, _) , (dst, _) in zip(self.in_files,self.os_files):
292
295
  file_size = os.stat(src).st_size
293
296
  pack_size = os.stat(dst).st_size
@@ -304,23 +307,22 @@ class CPKBuilder:
304
307
  pass
305
308
  self.os_files = []
306
309
 
307
- def save(self, outfile : str | BinaryIO, parallel : bool = False):
310
+ def save(self, outfile : str | BinaryIO, threads : int = 1):
308
311
  """Build and save the bundle into a file
309
312
 
310
313
 
311
314
  Args:
312
315
  outfile (str | BinaryIO): The output file path or a writable binary stream.
313
- parallel (bool, optional): Whether to use parallel processing for file compression (if at all used). Defaults to False.
316
+ threads (int, optional): The number of threads to use for file compression. Defaults to 1.
314
317
 
315
318
  NOTE:
316
319
  - Temporary files may be created during the process if compression is used.
317
- - parallel uses multiprocessing. Make sure your main function is guarded with `if __name__ == '__main__'` clause.
318
320
  """
319
321
  assert self.in_files, "cannot save empty bundle"
320
322
  self.outfile = outfile
321
323
  if type(outfile) == str:
322
324
  self.outfile = open(outfile, "wb")
323
- self._populate_files(parallel)
325
+ self._populate_files(threads)
324
326
  if self.encrypt:
325
327
  encflag = 0
326
328
  else:
PyCriCodecsEx/hca.py CHANGED
@@ -332,7 +332,7 @@ class HCACodec(HCA):
332
332
 
333
333
  Args:
334
334
  stream (str | bytes): Path to the HCA or WAV file, or a BinaryIO stream. WAV files will be automatically encoded with the given settings first.
335
- filename (str, optional): USM filename. Defaults to "default.hca".
335
+ filename (str, optional): Filename, used by USMBuilder. Defaults to "default.hca".
336
336
  quality (CriHcaQuality, optional): Encoding quality. Defaults to CriHcaQuality.High.
337
337
  key (int, optional): HCA key. Defaults to 0.
338
338
  subkey (int, optional): HCA subkey. Defaults to 0.
@@ -438,14 +438,17 @@ class HCACodec(HCA):
438
438
  p.strings = b"<NULL>\x00" + p.strings
439
439
  return p.bytes()
440
440
 
441
- def get_encoded(self):
441
+ def get_encoded(self) -> bytes:
442
442
  """Gets the encoded HCA audio data."""
443
443
  self.hcastream.seek(0)
444
444
  res = self.hcastream.read()
445
445
  self.hcastream.seek(0)
446
446
  return res
447
447
 
448
- def save(self, filepath: str):
449
- """Saves the decoded WAV audio to filepath"""
450
- with open(filepath, "wb") as f:
451
- f.write(self.decode())
448
+ def save(self, filepath: str | BinaryIO):
449
+ """Saves the decoded WAV audio to filepath or a writable stream"""
450
+ if type(filepath) == str:
451
+ with open(filepath, "wb") as f:
452
+ f.write(self.decode())
453
+ else:
454
+ filepath.write(self.decode())
PyCriCodecsEx/usm.py CHANGED
@@ -13,11 +13,7 @@ except ImportError:
13
13
  import tempfile
14
14
 
15
15
  # Big thanks and credit for k0lb3 and 9th helping me write this specific code.
16
- # Also credit for the original C++ code from Nyagamon/bnnm.
17
-
18
- # Apparently there is an older USM format called SofDec? This is for SofDec2 though.
19
- # Extraction working only for now, although check https://github.com/donmai-me/WannaCRI/
20
- # code for a complete breakdown of the USM format.
16
+ # Also credit for the original C++ code from Nyagamon/bnnm and https://github.com/donmai-me/WannaCRI/
21
17
 
22
18
  class USMCrypt:
23
19
  """USM related crypto functions"""
@@ -150,6 +146,7 @@ class USMCrypt:
150
146
  # are still unknown how to derive them, at least video wise it is possible, no idea how it's calculated audio wise nor anything else
151
147
  # seems like it could be random values and the USM would still work.
152
148
  class FFmpegCodec:
149
+ """Base codec for FFMpeg-based Video streams"""
153
150
  filename: str
154
151
  filesize: int
155
152
 
@@ -232,7 +229,7 @@ class FFmpegCodec:
232
229
  return len(self.packets)
233
230
 
234
231
  def frames(self):
235
- """frame data, frame dict, is keyframe, duration"""
232
+ """Generator of [frame data, frame dict, is keyframe, duration]"""
236
233
  offsets = [int(packet["pos"]) for packet in self.packets] + [self.filesize]
237
234
  for i, frame in enumerate(self.packets):
238
235
  frame_size = offsets[i + 1] - offsets[i]
@@ -290,13 +287,16 @@ class FFmpegCodec:
290
287
  return SFV_list
291
288
 
292
289
  def save(self, filepath: str):
293
- '''Saves the raw, underlying video stream to a file.'''
290
+ '''Saves the underlying video stream to a file.'''
294
291
  tell = self.file.tell()
295
292
  self.file.seek(0)
296
293
  shutil.copyfileobj(self.file, open(filepath, 'wb'))
297
294
  self.file.seek(tell)
298
295
 
299
296
  class VP9Codec(FFmpegCodec):
297
+ """VP9 Video stream codec.
298
+
299
+ Only streams with `.ivf` containers are supported."""
300
300
  MPEG_CODEC = 9
301
301
  MPEG_DCPREC = 0
302
302
  VERSION = 16777984
@@ -305,6 +305,9 @@ class VP9Codec(FFmpegCodec):
305
305
  super().__init__(filename)
306
306
  assert self.format == "ivf", "must be ivf format."
307
307
  class H264Codec(FFmpegCodec):
308
+ """H264 Video stream codec.
309
+
310
+ Only streams with `.h264` containers are supported."""
308
311
  MPEG_CODEC = 5
309
312
  MPEG_DCPREC = 11
310
313
  VERSION = 0
@@ -315,6 +318,9 @@ class H264Codec(FFmpegCodec):
315
318
  self.format == "h264"
316
319
  ), "must be raw h264 data. transcode with '.h264' suffix as output"
317
320
  class MPEG1Codec(FFmpegCodec):
321
+ """MPEG1 Video stream codec.
322
+
323
+ Only streams with `.mpeg1` containers are supported."""
318
324
  MPEG_CODEC = 1
319
325
  MPEG_DCPREC = 11
320
326
  VERSION = 0
@@ -482,7 +488,7 @@ class USM(USMCrypt):
482
488
  stmid = int.to_bytes(stmid, 4, 'big', signed='False')
483
489
  yield stmid, str(filename), self.output.get(f'{stmid.decode()}_{chno}', None)
484
490
 
485
- def get_video(self):
491
+ def get_video(self) -> VP9Codec | H264Codec | MPEG1Codec:
486
492
  """Create a video codec from the available streams.
487
493
 
488
494
  NOTE: A temporary file may be created with this process to determine the stream information."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyCriCodecsEx
3
- Version: 0.0.3
3
+ Version: 0.0.5
4
4
  Summary: Criware formats library for Python
5
5
  Home-page: https://mos9527.github.io/PyCriCodecsEx/
6
6
  Classifier: Programming Language :: Python :: 3
@@ -0,0 +1,15 @@
1
+ CriCodecsEx.cp312-win32.pyd,sha256=TmLC8PrEbJC24pGWvKOnq2CCFaFAiqGZTBo2Zwin5fk,68608
2
+ PyCriCodecsEx/__init__.py,sha256=vucW-Fe97d2P_nl7TMRJ2Cp458lb7VTRk6EckM_ozdM,23
3
+ PyCriCodecsEx/acb.py,sha256=_YpxIsdWW9vgz0KefKkb8zFnc2WKUwSTDqtgYaPDKp0,11956
4
+ PyCriCodecsEx/adx.py,sha256=LHr1YjVv6W0yzm1ZIGw54bM7rDW-Qozci0_GS8J4CrE,6130
5
+ PyCriCodecsEx/awb.py,sha256=SCTkKvn855bpr1I8Kq24P1es89I2unJaEGgDw7hg1Do,6395
6
+ PyCriCodecsEx/chunk.py,sha256=xmc92tONmXpdbOW2g-KckTeppTXFo8Km0J95UYbbvQg,2810
7
+ PyCriCodecsEx/cpk.py,sha256=er_eBLMG8eiXUvYp0h_lDWLN8Vx8CCDJ8COE9B5Rv_A,38125
8
+ PyCriCodecsEx/hca.py,sha256=TokIvd8IENZXxBw_3Na_o6vAoKvC8m_ooWIQ4qhmUqk,19603
9
+ PyCriCodecsEx/usm.py,sha256=Z6ETFy98Gl0KFot9zRE7Qg8_Dv9wsFUFybZgFvVXT_U,37173
10
+ PyCriCodecsEx/utf.py,sha256=0LBzpIbJiyL_4KOf_QpEJytr3PbHj8_M4Bd-Lay4wuk,28247
11
+ pycricodecsex-0.0.5.dist-info/licenses/LICENSE,sha256=B47Qr_82CmMyuL-wNFAH_P6PNJWaonv5dxo9MfgjEXw,1083
12
+ pycricodecsex-0.0.5.dist-info/METADATA,sha256=h3K2wCbl1h3WyL5-7AU6dFeW9hIxKcR3_FlFnf7KJWc,1291
13
+ pycricodecsex-0.0.5.dist-info/WHEEL,sha256=LwxTQZ0gyDP_uaeNCLm-ZIktY9hv6x0e22Q-hgFd-po,97
14
+ pycricodecsex-0.0.5.dist-info/top_level.txt,sha256=mSrrEse9hT0s6nl-sWAQWAhNRuZ6jo98pbFoN3L2MXk,26
15
+ pycricodecsex-0.0.5.dist-info/RECORD,,
@@ -1,15 +0,0 @@
1
- CriCodecsEx.cp312-win32.pyd,sha256=BnYjmDP-c1ZievQ_Vu33S3jPWaxiURwTJkI0OFg7bXg,68096
2
- PyCriCodecsEx/__init__.py,sha256=4dCrvStS1OgE3Vlcv-qx5-TAo377yOMEToEqCi-PwY4,23
3
- PyCriCodecsEx/acb.py,sha256=nOWggyQTPOBArzRp_7VRO9TY1diLxr2mRPKQd-sd2s0,10723
4
- PyCriCodecsEx/adx.py,sha256=s3gJBgKrwWjUahrxSJEy1GNHJrdz3iaylgr0TyTIi7M,5967
5
- PyCriCodecsEx/awb.py,sha256=t6owuEOH3Be31iBuEMwriOh0cPdVtpzsNbTPfLwg_cc,6363
6
- PyCriCodecsEx/chunk.py,sha256=xmc92tONmXpdbOW2g-KckTeppTXFo8Km0J95UYbbvQg,2810
7
- PyCriCodecsEx/cpk.py,sha256=bMJgvzPInM95R9dEdhHGW65jR9tFBKUacyOwZS2gOyA,38206
8
- PyCriCodecsEx/hca.py,sha256=BamujR3tRxMmgnPAYpUOamzOUOBs5hXWhkcBnj7Eeh4,19445
9
- PyCriCodecsEx/usm.py,sha256=LWSXXkfV1FZpymV0X6UH-lyS5gV6ecBHlLLXXXN9wMc,36972
10
- PyCriCodecsEx/utf.py,sha256=0LBzpIbJiyL_4KOf_QpEJytr3PbHj8_M4Bd-Lay4wuk,28247
11
- pycricodecsex-0.0.3.dist-info/licenses/LICENSE,sha256=B47Qr_82CmMyuL-wNFAH_P6PNJWaonv5dxo9MfgjEXw,1083
12
- pycricodecsex-0.0.3.dist-info/METADATA,sha256=buywcqSll1E63mnWNtEgwHiyAY8NbvMyP8CDLXAJLH0,1291
13
- pycricodecsex-0.0.3.dist-info/WHEEL,sha256=LwxTQZ0gyDP_uaeNCLm-ZIktY9hv6x0e22Q-hgFd-po,97
14
- pycricodecsex-0.0.3.dist-info/top_level.txt,sha256=mSrrEse9hT0s6nl-sWAQWAhNRuZ6jo98pbFoN3L2MXk,26
15
- pycricodecsex-0.0.3.dist-info/RECORD,,