mirror of
https://github.com/Hugoved/ism-downloader.git
synced 2026-07-15 18:50:03 +02:00
Update ism_parser.py
A better support for FPS has been added.
This commit is contained in:
+344
-1
@@ -4,6 +4,7 @@ import base64
|
|||||||
import binascii
|
import binascii
|
||||||
import re
|
import re
|
||||||
import inspect
|
import inspect
|
||||||
|
import bitstring
|
||||||
from struct import Struct
|
from struct import Struct
|
||||||
|
|
||||||
u8 = Struct(">B")
|
u8 = Struct(">B")
|
||||||
@@ -30,6 +31,348 @@ WIDEVINE_SYSTEM_ID = "edef8ba979d64acea3c827dcd51d21ed"
|
|||||||
START_CODE = b"\x00\x00\x00\x01"
|
START_CODE = b"\x00\x00\x00\x01"
|
||||||
|
|
||||||
|
|
||||||
|
def to_rbsp(oldStr):
|
||||||
|
newStr = oldStr
|
||||||
|
|
||||||
|
while True:
|
||||||
|
iter_matches = re.finditer("00000300|00000301|00000302", oldStr)
|
||||||
|
ll = [m.start(0) for m in iter_matches]
|
||||||
|
if ll == []:
|
||||||
|
break
|
||||||
|
|
||||||
|
indices = [x for x in ll if x & 1 == 0]
|
||||||
|
if indices == []:
|
||||||
|
break
|
||||||
|
|
||||||
|
s = 0
|
||||||
|
for k in indices:
|
||||||
|
tmpStr = newStr[0 : k - s] + oldStr[k:].replace("000003", "0000", 1)
|
||||||
|
s += 2
|
||||||
|
newStr = tmpStr
|
||||||
|
|
||||||
|
oldStr = newStr
|
||||||
|
|
||||||
|
return newStr
|
||||||
|
|
||||||
|
|
||||||
|
def read_past_scaling_matrix(s, matrix_size):
|
||||||
|
next_scale = 8
|
||||||
|
last_scale = 8
|
||||||
|
|
||||||
|
for _ in range(matrix_size):
|
||||||
|
if next_scale:
|
||||||
|
delta_scale = s.read("se")
|
||||||
|
next_scale = (last_scale + delta_scale + 256) & 0xFF
|
||||||
|
if next_scale:
|
||||||
|
last_scale = next_scale
|
||||||
|
|
||||||
|
|
||||||
|
def parse_avc_sps(data) -> dict:
|
||||||
|
spsLen = len(data)
|
||||||
|
ret = {}
|
||||||
|
|
||||||
|
c = bitstring.BitArray(bytes=data, length=spsLen * 8)
|
||||||
|
newStr = to_rbsp(c.hex)
|
||||||
|
s = bitstring.BitStream("0x" + newStr)
|
||||||
|
|
||||||
|
s.read("uint:16")
|
||||||
|
ret["profile"] = s.read("uint:8")
|
||||||
|
ret["profile_compatibility"] = s.read("uint:8")
|
||||||
|
ret["level"] = s.read("uint:8")
|
||||||
|
ret["parameter_id"] = s.read("ue")
|
||||||
|
ret["chroma_format_idc"] = 1
|
||||||
|
if ret["profile"] == 138:
|
||||||
|
ret["chroma_format_idc"] = 0
|
||||||
|
|
||||||
|
if ret["profile"] in [100, 110, 122, 244, 44, 83, 86, 118, 128, 138, 139, 134, 135]:
|
||||||
|
ret["chroma_format_idc"] = s.read("ue")
|
||||||
|
if ret["chroma_format_idc"] == 3:
|
||||||
|
ret["separate_colour_plane_flag"] = s.read("uint:1")
|
||||||
|
|
||||||
|
ret["bit_depth_luma"] = s.read("ue") + 8
|
||||||
|
ret["bit_depth_chroma"] = s.read("ue") + 8
|
||||||
|
ret["qp_prime_y_zero_transform_bypass_flag"] = s.read("uint:1")
|
||||||
|
ret["seq_scaling_matrix_present_flag"] = s.read("uint:1")
|
||||||
|
if ret["seq_scaling_matrix_present_flag"]:
|
||||||
|
for k in range(8):
|
||||||
|
seq_scaling_list_present_flag = s.read("uint:1")
|
||||||
|
if seq_scaling_list_present_flag:
|
||||||
|
if k < 6:
|
||||||
|
read_past_scaling_matrix(s, 16)
|
||||||
|
else:
|
||||||
|
read_past_scaling_matrix(s, 64)
|
||||||
|
|
||||||
|
ret["log2_max_frame_num"] = s.read("ue") + 4
|
||||||
|
ret["pic_order_cnt_type"] = s.read("ue")
|
||||||
|
if ret["pic_order_cnt_type"] == 0:
|
||||||
|
ret["log2_max_pic_order_cnt_lsb"] = s.read("ue") + 4
|
||||||
|
elif ret["pic_order_cnt_type"] == 1:
|
||||||
|
ret["delta_pic_order_always_zero_flag"] = s.read("uint:1")
|
||||||
|
ret["offset_for_non_ref_pic"] = s.read("ue")
|
||||||
|
ret["offset_for_top_to_bottom_field"] = s.read("ue")
|
||||||
|
num_ref_frames_in_pic_order_cnt_cycle = s.read("ue")
|
||||||
|
ret["ref_frames_in_pic_order_cnt_cycle"] = []
|
||||||
|
for _ in range(num_ref_frames_in_pic_order_cnt_cycle):
|
||||||
|
ret["ref_frames_in_pic_order_cnt_cycle"].append(s.read("ue"))
|
||||||
|
|
||||||
|
ret["num_ref_frames"] = s.read("ue")
|
||||||
|
ret["gaps_in_frame_num_value_allowed_flag"] = s.read("uint:1")
|
||||||
|
ret["width"] = (s.read("ue") + 1) * 16
|
||||||
|
ret["height"] = (s.read("ue") + 1) * 16
|
||||||
|
|
||||||
|
ret["frame_mbs_only_flag"] = frame_mbs_only = s.read("uint:1")
|
||||||
|
if not ret["frame_mbs_only_flag"]:
|
||||||
|
ret["mb_adaptive_frame_field_flag"] = s.read("uint:1")
|
||||||
|
ret["height"] *= 2
|
||||||
|
|
||||||
|
ret["direct_8x8_inference_flag"] = s.read("uint:1")
|
||||||
|
ret["frame_cropping_flag"] = s.read("uint:1")
|
||||||
|
|
||||||
|
if ret["frame_cropping_flag"]:
|
||||||
|
if ret["chroma_format_idc"] == 0:
|
||||||
|
crop_unit_x, crop_unit_y = 1, 2 - frame_mbs_only
|
||||||
|
elif ret["chroma_format_idc"] == 1:
|
||||||
|
crop_unit_x, crop_unit_y = 2, 2 * (2 - frame_mbs_only)
|
||||||
|
elif ret["chroma_format_idc"] == 2:
|
||||||
|
crop_unit_x, crop_unit_y = 2, 1 * (2 - frame_mbs_only)
|
||||||
|
elif ret["chroma_format_idc"] == 3:
|
||||||
|
crop_unit_x, crop_unit_y = 1, 1 * (2 - frame_mbs_only)
|
||||||
|
else:
|
||||||
|
raise ValueError("invalid chroma format idc")
|
||||||
|
|
||||||
|
ret["frame_crop_left_offset"] = s.read("ue")
|
||||||
|
ret["frame_crop_right_offset"] = s.read("ue")
|
||||||
|
ret["frame_crop_top_offset"] = s.read("ue")
|
||||||
|
ret["frame_crop_bottom_offset"] = s.read("ue")
|
||||||
|
|
||||||
|
frame_crop_width = ret["frame_crop_left_offset"] + ret["frame_crop_right_offset"]
|
||||||
|
frame_crop_height = ret["frame_crop_top_offset"] + ret["frame_crop_bottom_offset"]
|
||||||
|
ret["width"] -= frame_crop_width * crop_unit_x
|
||||||
|
ret["height"] -= frame_crop_height * crop_unit_y
|
||||||
|
|
||||||
|
ret["vui_parameters_present_flag"] = s.read("uint:1")
|
||||||
|
if ret["vui_parameters_present_flag"]:
|
||||||
|
ret["aspect_ratio_info_present_flag"] = s.read("uint:1")
|
||||||
|
if ret["aspect_ratio_info_present_flag"]:
|
||||||
|
ret["aspect_ratio_idc"] = s.read("uint:8")
|
||||||
|
if ret["aspect_ratio_idc"] == 255:
|
||||||
|
ret["sar_width"] = s.read("uint:16")
|
||||||
|
ret["sar_height"] = s.read("uint:16")
|
||||||
|
ret["overscan_info_present_flag"] = s.read("uint:1")
|
||||||
|
if ret["overscan_info_present_flag"]:
|
||||||
|
ret["overscan_appropriate_flag"] = s.read("uint:1")
|
||||||
|
ret["video_signal_type_present_flag"] = s.read("uint:1")
|
||||||
|
if ret["video_signal_type_present_flag"]:
|
||||||
|
ret["video_format"] = s.read("uint:3")
|
||||||
|
ret["video_full_range_flag"] = s.read("uint:1")
|
||||||
|
ret["colour_description_present_flag"] = s.read("uint:1")
|
||||||
|
if ret["colour_description_present_flag"]:
|
||||||
|
ret["colour_primaries"] = s.read("uint:8")
|
||||||
|
ret["transfer_characteristics"] = s.read("uint:8")
|
||||||
|
ret["matrix_coefficients"] = s.read("uint:8")
|
||||||
|
ret["chroma_loc_info_present_flag"] = s.read("uint:1")
|
||||||
|
if ret["chroma_loc_info_present_flag"]:
|
||||||
|
ret["chroma_sample_loc_type_top_field"] = s.read("ue")
|
||||||
|
ret["chroma_sample_loc_type_bottom_field"] = s.read("ue")
|
||||||
|
ret["vui_timing_info_present_flag"] = s.read("uint:1")
|
||||||
|
if ret["vui_timing_info_present_flag"]:
|
||||||
|
ret["vui_num_units_in_tick"] = s.read("uint:32")
|
||||||
|
ret["vui_time_scale"] = s.read("uint:32")
|
||||||
|
ret["vui_fixed_framerate_flag"] = s.read("uint:1")
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def parse_hevc_sps(data) -> dict:
|
||||||
|
spsLen = len(data)
|
||||||
|
ret = {}
|
||||||
|
|
||||||
|
c = bitstring.BitArray(bytes=data, length=spsLen * 8)
|
||||||
|
newStr = to_rbsp(c.hex)
|
||||||
|
s = bitstring.BitStream("0x" + newStr)
|
||||||
|
|
||||||
|
s.read("uint:16")
|
||||||
|
ret["sps_video_parameter_set_id"] = s.read("uint:4")
|
||||||
|
ret["sps_max_sub_layers"] = s.read("uint:3") + 1
|
||||||
|
ret["sps_temporal_id_nesting_flag"] = s.read("uint:1")
|
||||||
|
ret["general_profile_space"] = s.read("uint:2")
|
||||||
|
ret["general_tier_flag"] = s.read("uint:1")
|
||||||
|
ret["general_profile_idc"] = s.read("uint:5")
|
||||||
|
ret["general_compatibility_flags"] = s.read("uint:32")
|
||||||
|
ret["general_progressive_source_flag"] = s.read("uint:1")
|
||||||
|
ret["general_interlaced_source_flag"] = s.read("uint:1")
|
||||||
|
ret["general_non_packed_constraint_flag"] = s.read("uint:1")
|
||||||
|
ret["general_frame_only_constraint_flag"] = s.read("uint:1")
|
||||||
|
s.read("uint:32")
|
||||||
|
s.read("uint:12")
|
||||||
|
ret["general_level_idc"] = s.read("uint:8")
|
||||||
|
|
||||||
|
if ret["sps_max_sub_layers"] >= 2:
|
||||||
|
max_num_sub_layers = ret["sps_max_sub_layers"]
|
||||||
|
for _ in range(max_num_sub_layers - 1):
|
||||||
|
s.read("uint:1")
|
||||||
|
s.read("uint:1")
|
||||||
|
k = max_num_sub_layers - 1
|
||||||
|
while k < 8:
|
||||||
|
s.read("uint:2")
|
||||||
|
k += 1
|
||||||
|
|
||||||
|
ret["sps_seq_parameter_set_id"] = s.read("ue")
|
||||||
|
ret["chroma_format_idc"] = s.read("ue")
|
||||||
|
if ret["chroma_format_idc"] == 3:
|
||||||
|
ret["separate_colour_plane_flag"] = s.read("uint:1")
|
||||||
|
ret["pic_width_in_luma_samples"] = s.read("ue")
|
||||||
|
ret["pic_height_in_luma_samples"] = s.read("ue")
|
||||||
|
ret["conformance_window_flag"] = s.read("uint:1")
|
||||||
|
if ret["conformance_window_flag"]:
|
||||||
|
ret["conf_win_left_offset"] = s.read("ue")
|
||||||
|
ret["conf_win_right_offset"] = s.read("ue")
|
||||||
|
ret["conf_win_top_offset"] = s.read("ue")
|
||||||
|
ret["conf_win_bottom_offset"] = s.read("ue")
|
||||||
|
ret["bit_depth_luma"] = s.read("ue") + 8
|
||||||
|
ret["bit_depth_chroma"] = s.read("ue") + 8
|
||||||
|
ret["log2_max_pic_order_cnt_lsb"] = s.read("ue") + 4
|
||||||
|
ret["sps_sub_layer_ordering_info"] = s.read("uint:1")
|
||||||
|
|
||||||
|
if ret["sps_sub_layer_ordering_info"]:
|
||||||
|
k = 0
|
||||||
|
else:
|
||||||
|
k = ret["sps_max_sub_layers"] - 1
|
||||||
|
while k <= ret["sps_max_sub_layers"] - 1:
|
||||||
|
s.read("ue")
|
||||||
|
s.read("ue")
|
||||||
|
s.read("ue")
|
||||||
|
k += 1
|
||||||
|
|
||||||
|
ret["log2_min_coding_block_size"] = s.read("ue") + 3
|
||||||
|
ret["log2_diff_max_min_coding_block_size"] = s.read("ue")
|
||||||
|
ret["log2_min_transform_block_size"] = s.read("ue") + 2
|
||||||
|
ret["log2_diff_max_min_transform_block_size"] = s.read("ue")
|
||||||
|
ret["max_transform_hierarchy_depth_inter"] = s.read("ue")
|
||||||
|
ret["max_transform_hierarchy_depth_intra"] = s.read("ue")
|
||||||
|
ret["scaling_list_enabled_flag"] = s.read("uint:1")
|
||||||
|
assert ret["scaling_list_enabled_flag"] == 0
|
||||||
|
ret["amp_enabled_flag"] = s.read("uint:1")
|
||||||
|
ret["sample_adaptive_offset_enabled_flag"] = s.read("uint:1")
|
||||||
|
ret["pcm_enabled_flag"] = s.read("uint:1")
|
||||||
|
if ret["pcm_enabled_flag"]:
|
||||||
|
s.read("uint:4")
|
||||||
|
s.read("uint:4")
|
||||||
|
s.read("ue")
|
||||||
|
s.read("ue")
|
||||||
|
s.read("uint:1")
|
||||||
|
|
||||||
|
ret["num_short_term_ref_pic_sets"] = s.read("ue")
|
||||||
|
numRefs = 0
|
||||||
|
if ret["num_short_term_ref_pic_sets"] > 0:
|
||||||
|
for k in range(ret["num_short_term_ref_pic_sets"]):
|
||||||
|
inter_ref_pic_set_prediction_flag = 0
|
||||||
|
if k > 0:
|
||||||
|
inter_ref_pic_set_prediction_flag = s.read("uint:1")
|
||||||
|
if inter_ref_pic_set_prediction_flag:
|
||||||
|
refFrames = 0
|
||||||
|
for _ in range(numRefs + 1):
|
||||||
|
used_by_curr_pic_flag = s.read("uint:1")
|
||||||
|
if used_by_curr_pic_flag != 0:
|
||||||
|
refFrames += 1
|
||||||
|
numRefs = refFrames
|
||||||
|
else:
|
||||||
|
num_negative_pics = s.read("ue")
|
||||||
|
num_positive_pics = s.read("ue")
|
||||||
|
numRefs = num_negative_pics + num_positive_pics
|
||||||
|
for _ in range(num_negative_pics):
|
||||||
|
s.read("ue")
|
||||||
|
s.read("uint:1")
|
||||||
|
for _ in range(num_positive_pics):
|
||||||
|
s.read("ue")
|
||||||
|
s.read("uint:1")
|
||||||
|
|
||||||
|
ret["long_term_ref_pics_present_flag"] = s.read("uint:1")
|
||||||
|
if ret["long_term_ref_pics_present_flag"]:
|
||||||
|
num_long_term_ref_pics = s.read("ue")
|
||||||
|
for _ in range(num_long_term_ref_pics):
|
||||||
|
s.read(f"uint:{ret['log2_max_pic_order_cnt_lsb']}")
|
||||||
|
s.read("uint:1")
|
||||||
|
|
||||||
|
ret["sps_temporal_mvp_enable_flag"] = s.read("uint:1")
|
||||||
|
ret["sps_strong_intra_smoothing_enable_flag"] = s.read("uint:1")
|
||||||
|
ret["vui_parameters_present_flag"] = s.read("uint:1")
|
||||||
|
if ret["vui_parameters_present_flag"]:
|
||||||
|
ret["aspect_ratio_info_present_flag"] = s.read("uint:1")
|
||||||
|
if ret["aspect_ratio_info_present_flag"]:
|
||||||
|
ret["aspect_ratio_idc"] = s.read("uint:8")
|
||||||
|
if ret["aspect_ratio_idc"] == 255:
|
||||||
|
ret["sar_width"] = s.read("uint:16")
|
||||||
|
ret["sar_height"] = s.read("uint:16")
|
||||||
|
ret["overscan_info_present_flag"] = s.read("uint:1")
|
||||||
|
if ret["overscan_info_present_flag"]:
|
||||||
|
ret["overscan_appropriate_flag"] = s.read("uint:1")
|
||||||
|
ret["video_signal_type_present_flag"] = s.read("uint:1")
|
||||||
|
if ret["video_signal_type_present_flag"]:
|
||||||
|
ret["video_format"] = s.read("uint:3")
|
||||||
|
ret["video_full_range_flag"] = s.read("uint:1")
|
||||||
|
ret["colour_description_present_flag"] = s.read("uint:1")
|
||||||
|
if ret["colour_description_present_flag"]:
|
||||||
|
ret["colour_primaries"] = s.read("uint:8")
|
||||||
|
ret["transfer_characteristics"] = s.read("uint:8")
|
||||||
|
ret["matrix_coefficients"] = s.read("uint:8")
|
||||||
|
ret["chroma_loc_info_present_flag"] = s.read("uint:1")
|
||||||
|
if ret["chroma_loc_info_present_flag"]:
|
||||||
|
ret["chroma_sample_loc_type_top_field"] = s.read("ue")
|
||||||
|
ret["chroma_sample_loc_type_bottom_field"] = s.read("ue")
|
||||||
|
ret["neutral_chroma_indication_flag"] = s.read("uint:1")
|
||||||
|
ret["field_seq_flag"] = s.read("uint:1")
|
||||||
|
ret["frame_field_info_present_flag"] = s.read("uint:1")
|
||||||
|
ret["default_display_window_flag"] = s.read("uint:1")
|
||||||
|
if ret["default_display_window_flag"]:
|
||||||
|
ret["def_disp_win_left_offset"] = s.read("ue")
|
||||||
|
ret["def_disp_win_right_offset"] = s.read("ue")
|
||||||
|
ret["def_disp_win_top_offset"] = s.read("ue")
|
||||||
|
ret["def_disp_win_bottom_offset"] = s.read("ue")
|
||||||
|
ret["vui_timing_info_present_flag"] = s.read("uint:1")
|
||||||
|
if ret["vui_timing_info_present_flag"]:
|
||||||
|
ret["vui_num_units_in_tick"] = s.read("uint:32")
|
||||||
|
ret["vui_time_scale"] = s.read("uint:32")
|
||||||
|
ret["vui_poc_proportional_to_timing_flag"] = s.read("uint:1")
|
||||||
|
if ret["vui_poc_proportional_to_timing_flag"]:
|
||||||
|
ret["vui_num_ticks_poc_diff_one"] = s.read("ue") + 1
|
||||||
|
ret["hrd_parameters_present_flag"] = s.read("uint:1")
|
||||||
|
ret["sps_extension_flag"] = s.read("uint:1")
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def get_real_fps_from_codec_private_data(codec, codec_private_data):
|
||||||
|
if not codec_private_data:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
if isinstance(codec_private_data, bytes):
|
||||||
|
blob = bytes(codec_private_data)
|
||||||
|
else:
|
||||||
|
cleaned = str(codec_private_data).strip().replace(" ", "").replace("-", "")
|
||||||
|
if not cleaned:
|
||||||
|
return None
|
||||||
|
blob = binascii.unhexlify(cleaned.encode())
|
||||||
|
arr = blob.split(u32.pack(1))
|
||||||
|
codec_lower = str(codec or "").lower()
|
||||||
|
if codec_lower == "avc1":
|
||||||
|
sps = next((x for x in arr if x and (x[0] & 0x1F) == 7), None)
|
||||||
|
if not sps:
|
||||||
|
return None
|
||||||
|
sps_data = parse_avc_sps(sps)
|
||||||
|
if "vui_num_units_in_tick" in sps_data and "vui_time_scale" in sps_data and sps_data["vui_num_units_in_tick"]:
|
||||||
|
return sps_data["vui_time_scale"] / sps_data["vui_num_units_in_tick"] / 2
|
||||||
|
return None
|
||||||
|
if codec_lower in ("hvc1", "hev1", "dvhe", "dvh1"):
|
||||||
|
sps = next((x for x in arr if x and ((x[0] >> 1) == 0x21)), None)
|
||||||
|
if not sps:
|
||||||
|
return None
|
||||||
|
sps_data = parse_hevc_sps(sps)
|
||||||
|
if "vui_num_units_in_tick" in sps_data and "vui_time_scale" in sps_data and sps_data["vui_num_units_in_tick"]:
|
||||||
|
return sps_data["vui_time_scale"] / sps_data["vui_num_units_in_tick"]
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class BinaryWriter:
|
class BinaryWriter:
|
||||||
def __init__(self, stream):
|
def __init__(self, stream):
|
||||||
self.stream = stream
|
self.stream = stream
|
||||||
@@ -941,4 +1284,4 @@ def get_widevine_pssh_box(key_id):
|
|||||||
writer.WriteUInt(len(pssh_data))
|
writer.WriteUInt(len(pssh_data))
|
||||||
writer.Write(pssh_data)
|
writer.Write(pssh_data)
|
||||||
|
|
||||||
return full_box("pssh", 0, 0, stream.getvalue())
|
return full_box("pssh", 0, 0, stream.getvalue())
|
||||||
|
|||||||
Reference in New Issue
Block a user