PyCriCodecsEx 0.0.1__cp310-cp310-macosx_11_0_arm64.whl → 0.0.3__cp310-cp310-macosx_11_0_arm64.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.
- CriCodecsEx.cpython-310-darwin.so +0 -0
- PyCriCodecsEx/__init__.py +1 -1
- PyCriCodecsEx/acb.py +198 -19
- PyCriCodecsEx/adx.py +143 -4
- PyCriCodecsEx/awb.py +29 -14
- PyCriCodecsEx/chunk.py +24 -7
- PyCriCodecsEx/cpk.py +25 -16
- PyCriCodecsEx/hca.py +187 -38
- PyCriCodecsEx/usm.py +68 -339
- PyCriCodecsEx/utf.py +25 -37
- pycricodecsex-0.0.3.dist-info/METADATA +35 -0
- pycricodecsex-0.0.3.dist-info/RECORD +15 -0
- pycricodecsex-0.0.1.dist-info/METADATA +0 -81
- pycricodecsex-0.0.1.dist-info/RECORD +0 -15
- {pycricodecsex-0.0.1.dist-info → pycricodecsex-0.0.3.dist-info}/WHEEL +0 -0
- {pycricodecsex-0.0.1.dist-info → pycricodecsex-0.0.3.dist-info}/licenses/LICENSE +0 -0
- {pycricodecsex-0.0.1.dist-info → pycricodecsex-0.0.3.dist-info}/top_level.txt +0 -0
PyCriCodecsEx/usm.py
CHANGED
|
@@ -6,8 +6,6 @@ from functools import cached_property
|
|
|
6
6
|
|
|
7
7
|
from PyCriCodecsEx.chunk import *
|
|
8
8
|
from PyCriCodecsEx.utf import UTF, UTFBuilder
|
|
9
|
-
from PyCriCodecsEx.adx import ADX
|
|
10
|
-
from PyCriCodecsEx.hca import HCA
|
|
11
9
|
try:
|
|
12
10
|
import ffmpeg
|
|
13
11
|
except ImportError:
|
|
@@ -22,6 +20,7 @@ import tempfile
|
|
|
22
20
|
# code for a complete breakdown of the USM format.
|
|
23
21
|
|
|
24
22
|
class USMCrypt:
|
|
23
|
+
"""USM related crypto functions"""
|
|
25
24
|
videomask1: bytearray
|
|
26
25
|
videomask2: bytearray
|
|
27
26
|
audiomask: bytearray
|
|
@@ -162,6 +161,13 @@ class FFmpegCodec:
|
|
|
162
161
|
avbps: int
|
|
163
162
|
|
|
164
163
|
def __init__(self, stream: str | bytes):
|
|
164
|
+
"""Initialize FFmpegCodec with a media stream, gathering metadata and frame info.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
stream (str | bytes): The media stream to process.
|
|
168
|
+
NOTE:
|
|
169
|
+
A temp file maybe created for probing only. Which will be deleted after use.
|
|
170
|
+
"""
|
|
165
171
|
if type(stream) == str:
|
|
166
172
|
self.filename = stream
|
|
167
173
|
else:
|
|
@@ -317,249 +323,11 @@ class MPEG1Codec(FFmpegCodec):
|
|
|
317
323
|
super().__init__(stream)
|
|
318
324
|
assert self.format == "mpegvideo", "must be m1v format (mpegvideo)."
|
|
319
325
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
BASE_FRAMERATE = 2997 # dt = CHUNK_INTERVAL / BASE_FRAMERATE
|
|
323
|
-
AUDIO_CODEC = 4
|
|
324
|
-
METADATA_COUNT = 1
|
|
325
|
-
|
|
326
|
-
filename: str
|
|
327
|
-
|
|
328
|
-
chnls: int
|
|
329
|
-
sampling_rate: int
|
|
330
|
-
total_samples: int
|
|
331
|
-
avbps: int
|
|
332
|
-
|
|
333
|
-
filesize: int
|
|
334
|
-
|
|
335
|
-
def __init__(self, stream: str | bytes, filename: str, quality: CriHcaQuality = CriHcaQuality.High, key=0, subkey=0, **kwargs):
|
|
336
|
-
self.filename = filename
|
|
337
|
-
super().__init__(stream, key, subkey)
|
|
338
|
-
if self.filetype == "wav":
|
|
339
|
-
self.encode(
|
|
340
|
-
force_not_looping=True,
|
|
341
|
-
encrypt=key != 0,
|
|
342
|
-
keyless=False,
|
|
343
|
-
quality_level=quality
|
|
344
|
-
)
|
|
345
|
-
self.hcastream.seek(0, 2)
|
|
346
|
-
self.filesize = self.hcastream.tell()
|
|
347
|
-
self.hcastream.seek(0)
|
|
348
|
-
|
|
349
|
-
if self.filetype == "wav":
|
|
350
|
-
self.chnls = self.fmtChannelCount
|
|
351
|
-
self.sampling_rate = self.fmtSamplingRate
|
|
352
|
-
self.total_samples = int(self.dataSize // self.fmtSamplingSize)
|
|
353
|
-
else:
|
|
354
|
-
self.chnls = self.hca["ChannelCount"]
|
|
355
|
-
self.sampling_rate = self.hca["SampleRate"]
|
|
356
|
-
self.total_samples = self.hca["FrameCount"]
|
|
357
|
-
# I don't know how this is derived so I am putting my best guess here. TODO
|
|
358
|
-
self.avbps = int(self.filesize / self.chnls)
|
|
359
|
-
|
|
360
|
-
def generate_SFA(self, index: int, builder: "USMBuilder"):
|
|
361
|
-
current_interval = 0
|
|
362
|
-
padding = (
|
|
363
|
-
0x20 - (self.hca["HeaderSize"] % 0x20)
|
|
364
|
-
if self.hca["HeaderSize"] % 0x20 != 0
|
|
365
|
-
else 0
|
|
366
|
-
)
|
|
367
|
-
SFA_chunk = USMChunkHeader.pack(
|
|
368
|
-
USMChunckHeaderType.SFA.value,
|
|
369
|
-
self.hca["HeaderSize"] + 0x18 + padding,
|
|
370
|
-
0,
|
|
371
|
-
0x18,
|
|
372
|
-
padding,
|
|
373
|
-
index,
|
|
374
|
-
0,
|
|
375
|
-
0,
|
|
376
|
-
0,
|
|
377
|
-
current_interval,
|
|
378
|
-
self.BASE_FRAMERATE,
|
|
379
|
-
0,
|
|
380
|
-
0,
|
|
381
|
-
)
|
|
382
|
-
SFA_chunk += self.get_header().ljust(self.hca["HeaderSize"] + padding, b"\x00")
|
|
383
|
-
res = []
|
|
384
|
-
res.append(SFA_chunk)
|
|
385
|
-
for i, frame in enumerate(self.get_frames(), start=1):
|
|
386
|
-
padding = (
|
|
387
|
-
0x20 - (self.hca["FrameSize"] % 0x20)
|
|
388
|
-
if self.hca["FrameSize"] % 0x20 != 0
|
|
389
|
-
else 0
|
|
390
|
-
)
|
|
391
|
-
SFA_chunk = USMChunkHeader.pack(
|
|
392
|
-
USMChunckHeaderType.SFA.value,
|
|
393
|
-
self.hca["FrameSize"] + 0x18 + padding,
|
|
394
|
-
0,
|
|
395
|
-
0x18,
|
|
396
|
-
padding,
|
|
397
|
-
index,
|
|
398
|
-
0,
|
|
399
|
-
0,
|
|
400
|
-
0,
|
|
401
|
-
current_interval,
|
|
402
|
-
self.BASE_FRAMERATE,
|
|
403
|
-
0,
|
|
404
|
-
0,
|
|
405
|
-
)
|
|
406
|
-
SFA_chunk += frame[1].ljust(self.hca["FrameSize"] + padding, b"\x00")
|
|
407
|
-
current_interval = round(i * self.CHUNK_INTERVAL)
|
|
408
|
-
res.append(SFA_chunk)
|
|
409
|
-
else:
|
|
410
|
-
SFA_chunk = USMChunkHeader.pack(
|
|
411
|
-
USMChunckHeaderType.SFA.value,
|
|
412
|
-
0x38,
|
|
413
|
-
0,
|
|
414
|
-
0x18,
|
|
415
|
-
0,
|
|
416
|
-
index,
|
|
417
|
-
0,
|
|
418
|
-
0,
|
|
419
|
-
2,
|
|
420
|
-
0,
|
|
421
|
-
30,
|
|
422
|
-
0,
|
|
423
|
-
0,
|
|
424
|
-
)
|
|
425
|
-
SFA_chunk += b"#CONTENTS END ===============\x00"
|
|
426
|
-
res[-1] += SFA_chunk
|
|
427
|
-
|
|
428
|
-
return res
|
|
429
|
-
|
|
430
|
-
def get_metadata(self):
|
|
431
|
-
payload = [dict(hca_header=(UTFTypeValues.bytes, self.get_header()))]
|
|
432
|
-
p = UTFBuilder(payload, table_name="AUDIO_HEADER")
|
|
433
|
-
p.strings = b"<NULL>\x00" + p.strings
|
|
434
|
-
return p.bytes()
|
|
435
|
-
|
|
436
|
-
def save(self, filepath: str):
|
|
437
|
-
"""Saves the decoded WAV audio to filepath"""
|
|
438
|
-
with open(filepath, "wb") as f:
|
|
439
|
-
f.write(self.decode())
|
|
440
|
-
|
|
441
|
-
class ADXCodec(ADX):
|
|
442
|
-
CHUNK_INTERVAL = 99.9
|
|
443
|
-
BASE_FRAMERATE = 2997
|
|
444
|
-
# TODO: Move these to an enum
|
|
445
|
-
AUDIO_CODEC = 2
|
|
446
|
-
METADATA_COUNT = 0
|
|
447
|
-
|
|
448
|
-
filename : str
|
|
449
|
-
filesize : int
|
|
450
|
-
|
|
451
|
-
adx : bytes
|
|
452
|
-
header : bytes
|
|
453
|
-
sfaStream: BinaryIO
|
|
454
|
-
|
|
455
|
-
AdxDataOffset: int
|
|
456
|
-
AdxEncoding: int
|
|
457
|
-
AdxBlocksize: int
|
|
458
|
-
AdxSampleBitdepth: int
|
|
459
|
-
AdxChannelCount: int
|
|
460
|
-
AdxSamplingRate: int
|
|
461
|
-
AdxSampleCount: int
|
|
462
|
-
AdxHighpassFrequency: int
|
|
463
|
-
AdxVersion: int
|
|
464
|
-
AdxFlags: int
|
|
465
|
-
|
|
466
|
-
chnls: int
|
|
467
|
-
sampling_rate: int
|
|
468
|
-
total_samples: int
|
|
469
|
-
avbps: int
|
|
470
|
-
|
|
471
|
-
def __init__(self, stream: str | bytes, filename: str, bitdepth: int = 4, **kwargs):
|
|
472
|
-
if type(stream) == str:
|
|
473
|
-
self.adx = open(stream, "rb").read()
|
|
474
|
-
else:
|
|
475
|
-
self.adx = stream
|
|
476
|
-
self.filename = filename
|
|
477
|
-
self.filesize = len(self.adx)
|
|
478
|
-
magic = self.adx[:4]
|
|
479
|
-
if magic == b"RIFF":
|
|
480
|
-
self.adx = self.encode(self.adx, bitdepth, force_not_looping=True)
|
|
481
|
-
self.sfaStream = BytesIO(self.adx)
|
|
482
|
-
header = AdxHeaderStruct.unpack(self.sfaStream.read(AdxHeaderStruct.size))
|
|
483
|
-
FourCC, self.AdxDataOffset, self.AdxEncoding, self.AdxBlocksize, self.AdxSampleBitdepth, self.AdxChannelCount, self.AdxSamplingRate, self.AdxSampleCount, self.AdxHighpassFrequency, self.AdxVersion, self.AdxFlags = header
|
|
484
|
-
assert FourCC == 0x8000, "Either ADX or WAV is supported"
|
|
485
|
-
assert self.AdxVersion in {3,4}, "Unsupported ADX version"
|
|
486
|
-
if self.AdxVersion == 4:
|
|
487
|
-
self.sfaStream.seek(4 + 4 * self.AdxChannelCount, 1) # Padding + Hist values, they always seem to be 0.
|
|
488
|
-
self.sfaStream.seek(0)
|
|
489
|
-
self.chnls = self.AdxChannelCount
|
|
490
|
-
self.sampling_rate = self.AdxSamplingRate
|
|
491
|
-
self.total_samples = self.AdxSampleCount
|
|
492
|
-
self.avbps = int(self.filesize * 8 * self.chnls) - self.filesize
|
|
493
|
-
|
|
494
|
-
def generate_SFA(self, index: int, builder: "USMBuilder"):
|
|
495
|
-
current_interval = 0
|
|
496
|
-
stream_size = len(self.adx) - self.AdxBlocksize
|
|
497
|
-
chunk_size = int(self.AdxSamplingRate // (self.BASE_FRAMERATE / 100) // 32) * (self.AdxBlocksize * self.AdxChannelCount)
|
|
498
|
-
self.sfaStream.seek(0)
|
|
499
|
-
res = []
|
|
500
|
-
while self.sfaStream.tell() < stream_size:
|
|
501
|
-
if self.sfaStream.tell() > 0:
|
|
502
|
-
if self.sfaStream.tell() + chunk_size < stream_size:
|
|
503
|
-
datalen = chunk_size
|
|
504
|
-
else:
|
|
505
|
-
datalen = (stream_size - (self.AdxDataOffset + 4) - chunk_size) % chunk_size
|
|
506
|
-
else:
|
|
507
|
-
datalen = self.AdxDataOffset + 4
|
|
508
|
-
if not datalen:
|
|
509
|
-
break
|
|
510
|
-
padding = (0x20 - (datalen % 0x20) if datalen % 0x20 != 0 else 0)
|
|
511
|
-
SFA_chunk = USMChunkHeader.pack(
|
|
512
|
-
USMChunckHeaderType.SFA.value,
|
|
513
|
-
datalen + 0x18 + padding,
|
|
514
|
-
0,
|
|
515
|
-
0x18,
|
|
516
|
-
padding,
|
|
517
|
-
index,
|
|
518
|
-
0,
|
|
519
|
-
0,
|
|
520
|
-
0,
|
|
521
|
-
round(current_interval),
|
|
522
|
-
self.BASE_FRAMERATE,
|
|
523
|
-
0,
|
|
524
|
-
0
|
|
525
|
-
)
|
|
526
|
-
chunk_data = self.sfaStream.read(datalen)
|
|
527
|
-
if builder.encrypt_audio:
|
|
528
|
-
SFA_chunk = builder.AudioMask(chunk_data)
|
|
529
|
-
SFA_chunk += chunk_data.ljust(datalen + padding, b"\x00")
|
|
530
|
-
current_interval += self.CHUNK_INTERVAL
|
|
531
|
-
res.append(SFA_chunk)
|
|
532
|
-
# ---
|
|
533
|
-
SFA_chunk = USMChunkHeader.pack(
|
|
534
|
-
USMChunckHeaderType.SFA.value,
|
|
535
|
-
0x38,
|
|
536
|
-
0,
|
|
537
|
-
0x18,
|
|
538
|
-
0,
|
|
539
|
-
index,
|
|
540
|
-
0,
|
|
541
|
-
0,
|
|
542
|
-
2,
|
|
543
|
-
0,
|
|
544
|
-
30,
|
|
545
|
-
0,
|
|
546
|
-
0
|
|
547
|
-
)
|
|
548
|
-
SFA_chunk += b"#CONTENTS END ===============\x00"
|
|
549
|
-
res[-1] += SFA_chunk
|
|
550
|
-
return res
|
|
551
|
-
|
|
552
|
-
def get_metadata(self):
|
|
553
|
-
return None
|
|
554
|
-
|
|
555
|
-
def save(self, filepath: str):
|
|
556
|
-
"""Saves the encoded ADX audio to filepath"""
|
|
557
|
-
with open(filepath, "wb") as f:
|
|
558
|
-
f.write(self.decode(self.adx))
|
|
559
|
-
|
|
326
|
+
from PyCriCodecsEx.hca import HCACodec
|
|
327
|
+
from PyCriCodecsEx.adx import ADXCodec
|
|
560
328
|
|
|
561
329
|
class USM(USMCrypt):
|
|
562
|
-
"""
|
|
330
|
+
"""Use this class to extract infromation and data from a USM file."""
|
|
563
331
|
|
|
564
332
|
filename: str
|
|
565
333
|
decrypt: bool
|
|
@@ -574,7 +342,7 @@ class USM(USMCrypt):
|
|
|
574
342
|
|
|
575
343
|
metadata: list
|
|
576
344
|
|
|
577
|
-
def __init__(self, filename, key: str | int = None):
|
|
345
|
+
def __init__(self, filename : str | BinaryIO, key: str | int = None):
|
|
578
346
|
"""Loads a USM file into memory and prepares it for processing.
|
|
579
347
|
|
|
580
348
|
Args:
|
|
@@ -590,7 +358,10 @@ class USM(USMCrypt):
|
|
|
590
358
|
self._load_file()
|
|
591
359
|
|
|
592
360
|
def _load_file(self):
|
|
593
|
-
|
|
361
|
+
if type(self.filename) == str:
|
|
362
|
+
self.stream = open(self.filename, "rb")
|
|
363
|
+
else:
|
|
364
|
+
self.stream = self.filename
|
|
594
365
|
self.stream.seek(0, 2)
|
|
595
366
|
self.size = self.stream.tell()
|
|
596
367
|
self.stream.seek(0)
|
|
@@ -705,7 +476,7 @@ class USM(USMCrypt):
|
|
|
705
476
|
|
|
706
477
|
@property
|
|
707
478
|
def streams(self):
|
|
708
|
-
"""[Type (@SFV, @SFA),
|
|
479
|
+
"""Generator of Tuple[Stream Type ("@SFV", "@SFA"), File name, Raw stream data]"""
|
|
709
480
|
for stream in self.CRIDObj.dictarray[1:]:
|
|
710
481
|
filename, stmid, chno = stream["filename"][1], stream["stmid"][1], stream["chno"][1]
|
|
711
482
|
stmid = int.to_bytes(stmid, 4, 'big', signed='False')
|
|
@@ -729,7 +500,7 @@ class USM(USMCrypt):
|
|
|
729
500
|
stream.filename = sfname
|
|
730
501
|
return stream
|
|
731
502
|
|
|
732
|
-
def get_audios(self) -> List[HCACodec]:
|
|
503
|
+
def get_audios(self) -> List[ADXCodec | HCACodec]:
|
|
733
504
|
"""Create a list of audio codecs from the available streams."""
|
|
734
505
|
match self.audio_codec:
|
|
735
506
|
case ADXCodec.AUDIO_CODEC:
|
|
@@ -740,115 +511,73 @@ class USM(USMCrypt):
|
|
|
740
511
|
return []
|
|
741
512
|
|
|
742
513
|
class USMBuilder(USMCrypt):
|
|
743
|
-
"""
|
|
514
|
+
"""Use this class to build USM files."""
|
|
744
515
|
video_stream: VP9Codec | H264Codec | MPEG1Codec
|
|
745
|
-
|
|
746
|
-
enable_audio: bool
|
|
747
516
|
audio_streams: List[HCACodec | ADXCodec]
|
|
748
517
|
|
|
749
|
-
key: int
|
|
750
|
-
encrypt: bool
|
|
751
|
-
encrypt_audio: bool
|
|
518
|
+
key: int = None
|
|
519
|
+
encrypt: bool = False
|
|
520
|
+
encrypt_audio: bool = False
|
|
752
521
|
|
|
753
|
-
audio_codec: int
|
|
754
|
-
# !!: TODO Quality settings
|
|
755
522
|
def __init__(
|
|
756
523
|
self,
|
|
757
|
-
video: str,
|
|
758
|
-
audio: List[str] | str = None,
|
|
759
524
|
key = None,
|
|
760
|
-
|
|
761
|
-
encrypt_audio: bool = False,
|
|
525
|
+
encrypt_audio = False
|
|
762
526
|
) -> None:
|
|
763
527
|
"""Initialize the USMBuilder from set source files.
|
|
764
528
|
|
|
765
529
|
Args:
|
|
766
|
-
video (str): The path to the video file. The video source format will be used to map accordingly to the ones Sofdec use.
|
|
767
|
-
- MPEG1 (with M1V container): MPEG1 Codec (Sofdec Prime)
|
|
768
|
-
- H264 (with H264 container): H264 Codec
|
|
769
|
-
- VP9 (with IVF container): VP9 Codec
|
|
770
|
-
audio (List[str] | str, optional): The path(s) to the audio file(s). Defaults to None.
|
|
771
530
|
key (str | int, optional): The encryption key. Either int64 or a hex string. Defaults to None.
|
|
772
|
-
|
|
773
|
-
encrypt_audio (bool, optional): Whether to encrypt the audio. Defaults to False.
|
|
531
|
+
encrypt_audio (bool, optional): Whether to also encrypt the audio. Defaults to False.
|
|
774
532
|
"""
|
|
775
|
-
self.audio_codec = audio_codec
|
|
776
|
-
self.encrypt = False
|
|
777
|
-
self.enable_audio = False
|
|
778
|
-
self.encrypt_audio = encrypt_audio
|
|
779
|
-
self.key = 0
|
|
780
|
-
if encrypt_audio and not key:
|
|
781
|
-
raise ValueError("Cannot encrypt Audio without key.")
|
|
782
533
|
if key:
|
|
783
534
|
self.init_key(key)
|
|
784
535
|
self.encrypt = True
|
|
785
|
-
self.
|
|
536
|
+
self.encrypt_audio = encrypt_audio
|
|
786
537
|
self.audio_streams = []
|
|
787
|
-
if audio:
|
|
788
|
-
self.load_audio(audio)
|
|
789
|
-
self.enable_audio = True
|
|
790
|
-
|
|
791
|
-
def load_video(self, video):
|
|
792
|
-
temp_stream = FFmpegCodec(video)
|
|
793
|
-
self.video_stream = None
|
|
794
|
-
match temp_stream.stream["codec_name"]:
|
|
795
|
-
case "h264":
|
|
796
|
-
self.video_stream = H264Codec(video)
|
|
797
|
-
case "vp9":
|
|
798
|
-
self.video_stream = VP9Codec(video)
|
|
799
|
-
case "mpeg1video":
|
|
800
|
-
self.video_stream = MPEG1Codec(video)
|
|
801
|
-
assert self.video_stream, (
|
|
802
|
-
"fail to match suitable video codec. Codec=%s"
|
|
803
|
-
% temp_stream.stream["codec_name"]
|
|
804
|
-
)
|
|
805
538
|
|
|
806
|
-
def
|
|
807
|
-
|
|
808
|
-
if type(audio) == list:
|
|
809
|
-
count = 0
|
|
810
|
-
for track in audio:
|
|
811
|
-
if type(track) == str:
|
|
812
|
-
self.audio_filenames.append(os.path.basename(track))
|
|
813
|
-
else:
|
|
814
|
-
self.audio_filenames.append("{:02d}.sfa".format(count))
|
|
815
|
-
count += 1
|
|
816
|
-
else:
|
|
817
|
-
if type(audio) == str:
|
|
818
|
-
self.audio_filenames.append(os.path.basename(audio))
|
|
819
|
-
else:
|
|
820
|
-
self.audio_filenames.append("00.sfa")
|
|
539
|
+
def add_video(self, video : str | H264Codec | VP9Codec | MPEG1Codec):
|
|
540
|
+
"""Sets the video stream from the specified video file.
|
|
821
541
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
if
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
542
|
+
USMs only support one video stream. Consecutive calls to this method will replace the existing video stream.
|
|
543
|
+
|
|
544
|
+
When `video` is str - it will be treated as a file path. The video source format will be used to map accordingly to the ones Sofdec use.
|
|
545
|
+
- MPEG1 (with M1V container): MPEG1 Codec (Sofdec Prime)
|
|
546
|
+
- H264 (with H264 container): H264 Codec
|
|
547
|
+
- VP9 (with IVF container): VP9 Codec
|
|
548
|
+
|
|
549
|
+
Args:
|
|
550
|
+
video (str | FFmpegCodec): The path to the video file or an FFmpegCodec instance.
|
|
551
|
+
"""
|
|
552
|
+
if isinstance(video, str):
|
|
553
|
+
temp_stream = FFmpegCodec(video)
|
|
554
|
+
self.video_stream = None
|
|
555
|
+
match temp_stream.stream["codec_name"]:
|
|
556
|
+
case "h264":
|
|
557
|
+
self.video_stream = H264Codec(video)
|
|
558
|
+
case "vp9":
|
|
559
|
+
self.video_stream = VP9Codec(video)
|
|
560
|
+
case "mpeg1video":
|
|
561
|
+
self.video_stream = MPEG1Codec(video)
|
|
562
|
+
assert self.video_stream, (
|
|
563
|
+
"fail to match suitable video codec. Codec=%s"
|
|
564
|
+
% temp_stream.stream["codec_name"]
|
|
565
|
+
)
|
|
840
566
|
else:
|
|
841
|
-
|
|
842
|
-
fn = os.path.basename(audio)
|
|
843
|
-
else:
|
|
844
|
-
fn = "00.sfa"
|
|
845
|
-
hcaObj = codec(audio, fn, key=self.key)
|
|
846
|
-
self.audio_streams.append(hcaObj)
|
|
567
|
+
self.video_stream = video
|
|
847
568
|
|
|
569
|
+
def add_audio(self, audio : ADXCodec | HCACodec):
|
|
570
|
+
"""Append the audio stream(s) from the specified audio file(s).
|
|
571
|
+
|
|
572
|
+
Args:
|
|
573
|
+
audio (ADXCodec | HCACodec): The path(s) to the audio file(s).
|
|
574
|
+
"""
|
|
575
|
+
self.audio_streams.append(audio)
|
|
848
576
|
|
|
849
577
|
def build(self) -> bytes:
|
|
578
|
+
"""Build the USM payload"""
|
|
850
579
|
SFV_list = self.video_stream.generate_SFV(self)
|
|
851
|
-
if self.
|
|
580
|
+
if self.audio_streams:
|
|
852
581
|
SFA_chunks = [s.generate_SFA(i, self) for i, s in enumerate(self.audio_streams) ]
|
|
853
582
|
else:
|
|
854
583
|
SFA_chunks = []
|
|
@@ -928,7 +657,7 @@ class USMBuilder(USMCrypt):
|
|
|
928
657
|
)
|
|
929
658
|
CRIUSF_DIR_STREAM.append(video_dict)
|
|
930
659
|
|
|
931
|
-
if self.
|
|
660
|
+
if self.audio_streams:
|
|
932
661
|
chno = 0
|
|
933
662
|
for stream in self.audio_streams:
|
|
934
663
|
avbps = stream.avbps
|
|
@@ -936,7 +665,7 @@ class USMBuilder(USMCrypt):
|
|
|
936
665
|
minbuf += 27860
|
|
937
666
|
audio_dict = dict(
|
|
938
667
|
fmtver=(UTFTypeValues.uint, 0),
|
|
939
|
-
filename=(UTFTypeValues.string,
|
|
668
|
+
filename=(UTFTypeValues.string, stream.filename),
|
|
940
669
|
filesize=(UTFTypeValues.uint, stream.filesize),
|
|
941
670
|
datasize=(UTFTypeValues.uint, 0),
|
|
942
671
|
stmid=(
|
|
@@ -1018,7 +747,7 @@ class USMBuilder(USMCrypt):
|
|
|
1018
747
|
|
|
1019
748
|
audio_metadata = []
|
|
1020
749
|
audio_headers = []
|
|
1021
|
-
if self.
|
|
750
|
+
if self.audio_streams:
|
|
1022
751
|
chno = 0
|
|
1023
752
|
for stream in self.audio_streams:
|
|
1024
753
|
metadata = stream.get_metadata()
|
|
@@ -1141,7 +870,7 @@ class USMBuilder(USMCrypt):
|
|
|
1141
870
|
VIDEO_HDRINFO = gen_video_hdr_info(len(VIDEO_SEEKINFO))
|
|
1142
871
|
|
|
1143
872
|
total_len = sum([len(x) for x in SFV_list]) + first_chk_ofs
|
|
1144
|
-
if self.
|
|
873
|
+
if self.audio_streams:
|
|
1145
874
|
sum_len = 0
|
|
1146
875
|
for stream in SFA_chunks:
|
|
1147
876
|
for x in stream:
|
|
@@ -1181,7 +910,7 @@ class USMBuilder(USMCrypt):
|
|
|
1181
910
|
|
|
1182
911
|
# Header chunks
|
|
1183
912
|
header += VIDEO_HDRINFO
|
|
1184
|
-
if self.
|
|
913
|
+
if self.audio_streams:
|
|
1185
914
|
header += b''.join(audio_headers)
|
|
1186
915
|
SFV_END = USMChunkHeader.pack(
|
|
1187
916
|
USMChunckHeaderType.SFV.value,
|
|
@@ -1202,7 +931,7 @@ class USMBuilder(USMCrypt):
|
|
|
1202
931
|
header += SFV_END
|
|
1203
932
|
|
|
1204
933
|
SFA_chk_END = b'' # Maybe reused
|
|
1205
|
-
if self.
|
|
934
|
+
if self.audio_streams:
|
|
1206
935
|
SFA_chk_END = b''.join([
|
|
1207
936
|
USMChunkHeader.pack(
|
|
1208
937
|
USMChunckHeaderType.SFA.value,
|
|
@@ -1223,7 +952,7 @@ class USMBuilder(USMCrypt):
|
|
|
1223
952
|
header += SFA_chk_END # Ends audio_headers
|
|
1224
953
|
header += VIDEO_SEEKINFO
|
|
1225
954
|
|
|
1226
|
-
if self.
|
|
955
|
+
if self.audio_streams:
|
|
1227
956
|
header += b''.join(audio_metadata)
|
|
1228
957
|
SFV_END = USMChunkHeader.pack(
|
|
1229
958
|
USMChunckHeaderType.SFV.value,
|