mirror of
https://github.com/nirvana-7777/script.service.ultimate.git
synced 2026-10-04 15:02:26 +02:00
Add m3u playlists
This commit is contained in:
@@ -70,38 +70,26 @@ class PSSHOffsets:
|
||||
|
||||
class TencOffsets:
|
||||
"""
|
||||
Track Encryption Box (tenc) Binary Structure Offsets
|
||||
tenc Box Structure (based on real-world implementation)
|
||||
|
||||
tenc Box Format (ISO/IEC 23001-7):
|
||||
[0-3] Box size (uint32)
|
||||
[4-7] Box type ('tenc')
|
||||
[8] Version (uint8)
|
||||
[9-11] Flags (uint24)
|
||||
[12-15] Reserved (uint24) + default_crypt_byte_block (uint8)
|
||||
|
||||
Version 0:
|
||||
[16] Reserved (uint8)
|
||||
[17] default_is_protected (uint8)
|
||||
[18] default_per_sample_IV_size (uint8)
|
||||
[19-34] default_KID (16 bytes)
|
||||
|
||||
Version 1:
|
||||
[16] default_constant_IV_size (uint8)
|
||||
[17+] default_constant_IV (if size > 0)
|
||||
[...] default_KID (16 bytes, after IV)
|
||||
Data section (after 8-byte header):
|
||||
[0-3] version + flags (uint32)
|
||||
[4-6] reserved (3 bytes)
|
||||
[7] default_is_protected (uint8)
|
||||
[8] default_per_sample_IV_size (uint8)
|
||||
[9-24] default_KID (16 bytes)
|
||||
"""
|
||||
BOX_SIZE = 0
|
||||
BOX_TYPE = 4
|
||||
BOX_TYPE_END = 8
|
||||
VERSION = 8
|
||||
# Data section offsets
|
||||
VERSION_FLAGS = 0
|
||||
VERSION_FLAGS_END = 4
|
||||
IS_PROTECTED = 7
|
||||
IV_SIZE = 8
|
||||
KID_START = 9
|
||||
KID_END = 25 # 9 + 16 = 25
|
||||
|
||||
# Version 0 specific
|
||||
V0_IS_PROTECTED = 17
|
||||
V0_IV_SIZE = 18
|
||||
V0_KID_START = 19
|
||||
V0_KID_END = 35
|
||||
|
||||
MIN_TENC_V0_SIZE = 35 # Minimum valid V0 tenc box
|
||||
# Minimum data size (not including 8-byte header)
|
||||
MIN_TENC_DATA_SIZE = 24 # Up to byte 23, but KID needs byte 24
|
||||
MIN_TENC_TOTAL_SIZE = 32 # 8 header + 24 data
|
||||
|
||||
|
||||
# DRM System UUID Mappings (normalized, no hyphens)
|
||||
|
||||
@@ -1,217 +1,75 @@
|
||||
"""
|
||||
tenc Box Parser
|
||||
|
||||
Handles parsing of Track Encryption (tenc) boxes for extracting Key IDs
|
||||
when PSSH boxes don't contain them (version 0 PSSH).
|
||||
Simplified parser based on working implementation that successfully
|
||||
handles real-world streams without strict ISO spec compliance.
|
||||
"""
|
||||
|
||||
import struct
|
||||
|
||||
from .constants import TencOffsets, MAX_TENC_SIZE, KID_SIZE_BYTES
|
||||
from .exceptions import InvalidTencError
|
||||
from .utils import bytes_to_uuid_hex
|
||||
from ...utils import logger
|
||||
|
||||
|
||||
class TencParser:
|
||||
"""
|
||||
Parser for tenc (Track Encryption) boxes.
|
||||
|
||||
Used as fallback when PSSH version 0 doesn't contain Key IDs.
|
||||
According to ISO/IEC 23001-7, tenc boxes contain default encryption
|
||||
information including the default Key ID.
|
||||
"""
|
||||
"""Parser for tenc (Track Encryption) boxes."""
|
||||
|
||||
@staticmethod
|
||||
def extract_kids_from_tenc(tenc_data: bytes) -> list[str]:
|
||||
"""
|
||||
Extract Key IDs from tenc box data.
|
||||
|
||||
This is typically used as a fallback when PSSH version 0 boxes
|
||||
don't contain Key IDs directly.
|
||||
Based on working code that extracts KID from bytes 9-25
|
||||
after validating is_protected at byte 7.
|
||||
|
||||
Args:
|
||||
tenc_data: Raw tenc box bytes
|
||||
tenc_data: Raw tenc box data (starting after 8-byte header)
|
||||
|
||||
Returns:
|
||||
List of normalized Key IDs (32 hex chars, no hyphens)
|
||||
|
||||
Raises:
|
||||
InvalidTencError: If tenc box is malformed
|
||||
List containing single Key ID if found, empty list otherwise
|
||||
"""
|
||||
if not tenc_data:
|
||||
raise InvalidTencError("tenc box data cannot be empty")
|
||||
|
||||
# Check size limits
|
||||
if len(tenc_data) > MAX_TENC_SIZE:
|
||||
raise InvalidTencError(
|
||||
f"tenc box too large: {len(tenc_data)} bytes (max: {MAX_TENC_SIZE})"
|
||||
)
|
||||
|
||||
# Validate minimum size for version 0
|
||||
if len(tenc_data) < TencOffsets.MIN_TENC_V0_SIZE:
|
||||
raise InvalidTencError(
|
||||
f"tenc box too small: {len(tenc_data)} bytes "
|
||||
f"(minimum: {TencOffsets.MIN_TENC_V0_SIZE})"
|
||||
)
|
||||
|
||||
# Validate box type
|
||||
box_type = tenc_data[TencOffsets.BOX_TYPE:TencOffsets.BOX_TYPE_END]
|
||||
if box_type != b"tenc":
|
||||
raise InvalidTencError(
|
||||
f"Not a tenc box: type is '{box_type.decode('ascii', errors='ignore')}'"
|
||||
)
|
||||
|
||||
# Parse version
|
||||
version = tenc_data[TencOffsets.VERSION]
|
||||
|
||||
# Extract KID based on version
|
||||
if version == 0:
|
||||
return TencParser._extract_kid_from_v0_tenc(tenc_data)
|
||||
elif version == 1:
|
||||
return TencParser._extract_kid_from_v1_tenc(tenc_data)
|
||||
else:
|
||||
raise InvalidTencError(f"Unsupported tenc version: {version}")
|
||||
|
||||
@staticmethod
|
||||
def _extract_kid_from_v0_tenc(tenc_data: bytes) -> list[str]:
|
||||
"""
|
||||
Extract Key ID from version 0 tenc box.
|
||||
|
||||
Version 0 structure:
|
||||
[16] Reserved (uint8)
|
||||
[17] default_is_protected (uint8)
|
||||
[18] default_per_sample_IV_size (uint8)
|
||||
[19-34] default_KID (16 bytes)
|
||||
|
||||
Args:
|
||||
tenc_data: Raw tenc box bytes
|
||||
|
||||
Returns:
|
||||
List containing single normalized Key ID
|
||||
|
||||
Raises:
|
||||
InvalidTencError: If tenc structure is invalid
|
||||
"""
|
||||
if len(tenc_data) < TencOffsets.V0_KID_END:
|
||||
raise InvalidTencError(
|
||||
f"tenc v0 box truncated: {len(tenc_data)} bytes "
|
||||
f"(need at least {TencOffsets.V0_KID_END})"
|
||||
)
|
||||
|
||||
# Check if track is protected
|
||||
is_protected = tenc_data[TencOffsets.V0_IS_PROTECTED]
|
||||
if is_protected == 0:
|
||||
# Unencrypted track, no KID
|
||||
return []
|
||||
|
||||
# Extract KID bytes
|
||||
kid_bytes = tenc_data[TencOffsets.V0_KID_START:TencOffsets.V0_KID_END]
|
||||
# Size check based on working code
|
||||
if len(tenc_data) < 24:
|
||||
logger.debug(f"tenc data too small: {len(tenc_data)} bytes (need 24)")
|
||||
return []
|
||||
|
||||
# tenc KIDs are typically in UUID/GUID format (mixed-endian)
|
||||
try:
|
||||
kid_hex = TencParser._parse_guid_bytes(kid_bytes)
|
||||
except Exception:
|
||||
# Fallback to raw hex if GUID parsing fails
|
||||
kid_hex = bytes_to_uuid_hex(kid_bytes)
|
||||
# Version and flags are at start but we don't really need them
|
||||
# version_flags = struct.unpack(">I", tenc_data[0:4])[0]
|
||||
# version = (version_flags >> 24) & 0xFF
|
||||
|
||||
return [kid_hex]
|
||||
# Check if track is protected (byte 7)
|
||||
if len(tenc_data) > 7:
|
||||
is_protected = tenc_data[7]
|
||||
if is_protected == 0:
|
||||
logger.debug("Track not encrypted (is_protected=0)")
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def _extract_kid_from_v1_tenc(tenc_data: bytes) -> list[str]:
|
||||
"""
|
||||
Extract Key ID from version 1 tenc box.
|
||||
# Extract KID from bytes 9-24
|
||||
if len(tenc_data) >= 25:
|
||||
kid_bytes = tenc_data[9:25]
|
||||
|
||||
Version 1 structure:
|
||||
[16] default_constant_IV_size (uint8)
|
||||
[17+] default_constant_IV (if IV_size > 0)
|
||||
[...] default_KID (16 bytes, after IV)
|
||||
# Working code uses simple hexlify
|
||||
kid_hex = kid_bytes.hex().lower()
|
||||
|
||||
Args:
|
||||
tenc_data: Raw tenc box bytes
|
||||
logger.debug(f"Extracted KID: {kid_hex[:8]}...")
|
||||
return [kid_hex]
|
||||
else:
|
||||
logger.debug(f"tenc data too short for KID: {len(tenc_data)} bytes")
|
||||
return []
|
||||
|
||||
Returns:
|
||||
List containing single normalized Key ID
|
||||
|
||||
Raises:
|
||||
InvalidTencError: If tenc structure is invalid
|
||||
"""
|
||||
# Read IV size
|
||||
if len(tenc_data) < 17:
|
||||
raise InvalidTencError("tenc v1 box too small to read IV size")
|
||||
|
||||
iv_size = tenc_data[16]
|
||||
|
||||
# KID starts after IV
|
||||
kid_start = 17 + iv_size
|
||||
kid_end = kid_start + KID_SIZE_BYTES
|
||||
|
||||
if len(tenc_data) < kid_end:
|
||||
raise InvalidTencError(
|
||||
f"tenc v1 box truncated: {len(tenc_data)} bytes "
|
||||
f"(need at least {kid_end} for KID)"
|
||||
)
|
||||
|
||||
# Extract KID bytes
|
||||
kid_bytes = tenc_data[kid_start:kid_end]
|
||||
|
||||
# tenc KIDs are typically in UUID/GUID format
|
||||
try:
|
||||
kid_hex = TencParser._parse_guid_bytes(kid_bytes)
|
||||
except Exception:
|
||||
kid_hex = bytes_to_uuid_hex(kid_bytes)
|
||||
|
||||
return [kid_hex]
|
||||
|
||||
@staticmethod
|
||||
def _parse_guid_bytes(data: bytes) -> str:
|
||||
"""
|
||||
Parse GUID/UUID bytes with mixed-endian format (Microsoft GUID).
|
||||
|
||||
GUID byte order:
|
||||
- Data1 (4 bytes): little-endian
|
||||
- Data2 (2 bytes): little-endian
|
||||
- Data3 (2 bytes): little-endian
|
||||
- Data4 (8 bytes): big-endian
|
||||
|
||||
Args:
|
||||
data: 16 bytes of GUID data
|
||||
|
||||
Returns:
|
||||
32-character hex string (normalized, no hyphens)
|
||||
"""
|
||||
if len(data) != 16:
|
||||
raise ValueError(f"GUID bytes must be 16 bytes (got {len(data)})")
|
||||
|
||||
# Parse with mixed-endian format (Microsoft GUID)
|
||||
data1 = struct.unpack("<I", data[0:4])[0] # Little-endian uint32
|
||||
data2 = struct.unpack("<H", data[4:6])[0] # Little-endian uint16
|
||||
data3 = struct.unpack("<H", data[6:8])[0] # Little-endian uint16
|
||||
data4 = data[8:16].hex() # Big-endian (raw bytes)
|
||||
|
||||
# Format as hex string
|
||||
guid_hex = f"{data1:08x}{data2:04x}{data3:04x}{data4}"
|
||||
|
||||
return guid_hex.lower()
|
||||
except Exception as e:
|
||||
logger.debug(f"Failed to extract KID from tenc: {e}")
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def is_track_encrypted(tenc_data: bytes) -> bool:
|
||||
"""
|
||||
Check if track is encrypted based on tenc box.
|
||||
|
||||
Args:
|
||||
tenc_data: Raw tenc box bytes
|
||||
|
||||
Returns:
|
||||
True if track is encrypted, False otherwise
|
||||
"""
|
||||
if not tenc_data or len(tenc_data) < TencOffsets.V0_IS_PROTECTED + 1:
|
||||
"""Check if track is encrypted based on is_protected flag."""
|
||||
if not tenc_data or len(tenc_data) < 8:
|
||||
return False
|
||||
|
||||
version = tenc_data[TencOffsets.VERSION]
|
||||
|
||||
if version == 0:
|
||||
is_protected = tenc_data[TencOffsets.V0_IS_PROTECTED]
|
||||
try:
|
||||
is_protected = tenc_data[7] if len(tenc_data) > 7 else 0
|
||||
return is_protected != 0
|
||||
|
||||
# Version 1+ tracks are assumed encrypted if tenc box exists
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
Reference in New Issue
Block a user