diff --git a/LICENSE.flashmedia b/LICENSE.flashmedia new file mode 100644 index 00000000..2e543c35 --- /dev/null +++ b/LICENSE.flashmedia @@ -0,0 +1,23 @@ +Copyright (c) 2011-2013, Christopher Rosell +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/src/livestreamer/packages/flashmedia/amf0.py b/src/livestreamer/packages/flashmedia/amf0.py index fa4368e0..2195d569 100644 --- a/src/livestreamer/packages/flashmedia/amf0.py +++ b/src/livestreamer/packages/flashmedia/amf0.py @@ -1,6 +1,7 @@ from .compat import * from .error import * from .packet import * +from .types import * from .util import * class AMF0Header(Packet): @@ -12,23 +13,23 @@ def __init__(self, name, value, must_understand=False): @property def size(self): size = 4+1 - size += PacketIO.script_string_size(self.name) - size += PacketIO.script_value_size(self.value) + size += ScriptDataString.size(self.name) + size += ScriptDataValue.size(self.value) return size def _serialize(self, packet): - packet.write_script_string(self.name) - packet.write_u8(int(self.must_understand)) - packet.write_u32(self.size) - packet.write_script_value(self.value) + packet += ScriptDataString(self.name) + packet += U8(int(self.must_understand)) + packet += U32BE(self.size) + packet += ScriptDataValue(self.value) @classmethod def _deserialize(cls, io): - name = io.read_script_string() - must_understand = bool(io.read_u8()) - length = io.read_u32() - value = io.read_script_value() + name = ScriptDataString.read(io) + must_understand = bool(U8.read(io)) + length = U32BE.read(io) + value = ScriptDataValue.read(io) return cls(name, value, must_understand) @@ -42,25 +43,24 @@ def __init__(self, target_uri, response_uri, value): @property def size(self): size = 4 - size += PacketIO.script_string_size(self.target_uri) - size += PacketIO.script_string_size(self.response_uri) - size += PacketIO.script_value_size(self.value) + size += ScriptDataString.size(self.target_uri) + size += ScriptDataString.size(self.response_uri) + size += ScriptDataValue.size(self.value) return size def _serialize(self, packet): - packet.write_script_string(self.target_uri) - packet.write_script_string(self.response_uri) - packet.write_u32(self.size) - packet.write_script_value(self.value) + packet += ScriptDataString(self.target_uri) + packet += ScriptDataString(self.response_uri) + packet += U32BE(self.size) + packet += ScriptDataValue(self.value) @classmethod def _deserialize(cls, io): - target_uri = io.read_script_string() - response_uri = io.read_script_string() - length = io.read_u32() - value = io.read_script_value() - + target_uri = ScriptDataString.read(io) + response_uri = ScriptDataString.read(io) + length = U32BE.read(io) + value = ScriptDataValue.read(io) return cls(target_uri, response_uri, value) @@ -84,34 +84,34 @@ def size(self): return size def _serialize(self, packet): - packet.write_u16(self.version) - packet.write_u16(len(self.headers)) + packet += U16BE(self.version) + packet += U16BE(len(self.headers)) for header in self.headers: header.serialize(packet) - packet.write_u16(len(self.messages)) + packet += U16BE(len(self.messages)) for message in self.messages: message.serialize(packet) @classmethod def _deserialize(cls, io): - version = io.read_u16() + version = U16BE.read(io) if version != 0: raise AMFError("AMF version must be 0") headers = [] - header_count = io.read_u16() + header_count = U16BE.read(io) for i in range(header_count): - header = AMF0Header.deserialize(io=io) + header = AMF0Header.deserialize(io) headers.append(header) messages = [] - message_count = io.read_u16() + message_count = U16BE.read(io) for i in range(message_count): - message = AMF0Message.deserialize(io=io) + message = AMF0Message.deserialize(io) messages.append(message) return cls(version, headers, messages) diff --git a/src/livestreamer/packages/flashmedia/box.py b/src/livestreamer/packages/flashmedia/box.py index 7ac8b253..c6a0cd64 100644 --- a/src/livestreamer/packages/flashmedia/box.py +++ b/src/livestreamer/packages/flashmedia/box.py @@ -1,14 +1,16 @@ from ctypes import BigEndianStructure, Union, c_uint8, c_uint16, c_uint32 +from io import BytesIO from .compat import * from .error import * from .packet import * +from .types import * from .util import * + class Box(Packet): - def __init__(self, type, total_size, payload, extended_size=False): + def __init__(self, type, payload, extended_size=False): self.type = type - self.total_size = total_size self.payload = payload self.extended_size = extended_size @@ -23,42 +25,30 @@ def size(self): return size @classmethod - def _deserialize(cls, io, strict=False, preload=True): - size = io.read_u32() - type_ = io.read_padded(4) + def _deserialize(cls, io, strict=False, raw_payload=False): + size = U32BE.read(io) + type_ = FourCC.read(io) header_size = 8 extended_size = False if size == 1: - size = io.read_u64() + size = U64BE.read(io) header_size += 8 extended_size = True - if type_ in PayloadTypes: - parent_data_left = io.data_left - io.data_left = size - header_size + if size == 0: + data = io.read() + else: + data = chunked_read(io, size - header_size, exception=F4VError) + if type_ in PayloadTypes and not raw_payload: payloadcls = PayloadTypes[type_] - - if issubclass(payloadcls, RawPayload): - payload = payloadcls.deserialize(io=io, - preload=preload) - else: - payload = payloadcls.deserialize(io=io) - - if parent_data_left is not None: - io.data_left = parent_data_left - payload.size - elif preload: - io.data_left = None + payloadio = BytesIO(data) + payload = payloadcls.deserialize(payloadio) else: - if size == 0: - data = io.read() - else: - data = io.read(size - header_size) - payload = RawPayload(data) - box = cls(type_, size, payload, extended_size) + box = cls(type_, payload, extended_size) if strict and box.size != size: raise F4VError("Data size mismatch when deserialising tag") @@ -69,19 +59,19 @@ def _serialize(self, packet): size = self.payload.size if size > 0xFFFFFFFF or self.extended_size: - packet.write_u32(1) + packet += U32BE(1) else: - packet.write_u32(size + 8) + packet += U32BE(size + 8) - packet.write_padded(self.type, 4) + packet += FourCC(self.type) if size > 0xFFFFFFFF or self.extended_size: - packet.write_u64(size + 16) + packet += U64BE(size + 16) if isinstance(self.payload, BoxPayload): self.payload.serialize(packet) else: - packet.write(self.payload) + packetwrite(self.payload) class BoxPayload(Packet): @property @@ -121,8 +111,12 @@ def _serialize(self, packet): def _deserialize(cls, io): boxes = [] - while io.data_left > 0: - box = Box.deserialize(io=io) + while True: + try: + box = Box.deserialize(io) + except IOError: + break + boxes.append(box) return cls(boxes) @@ -141,14 +135,13 @@ def _serialize(self, packet): @classmethod def _deserialize(cls, io): - box = Box.deserialize(io=io) + box = Box.deserialize(io) return cls(box) class RawPayload(BoxPayload): - def __init__(self, data, io=None): + def __init__(self, data): self.data = data - self.io = io def __repr__(self): return "".format(self.size) @@ -158,23 +151,12 @@ def size(self): return len(self.data) @classmethod - def _deserialize(cls, io, preload=True): - if preload: - data = io.read() - return cls(data) - else: - data = bytearray(io.data_left) - return cls(data, io=io) - - def read(self, size=-1): - offset = len(self.data) - self.io.data_left - data = self.io.read(size) - self.data[offset:offset+len(data)] = data - - return data + def _deserialize(cls, io): + data = io.read() + return cls(data) def _serialize(self, packet): - packet.write(self.data) + packet += self.data class BoxPayloadFTYP(BoxPayload): def __init__(self, major_brand="f4v", minor_version=0, @@ -188,20 +170,24 @@ def size(self): return 4+4+(len(self.compatible_brands)*4) def _serialize(self, packet): - packet.write_padded(self.major_brand, 4) - packet.write_u32(self.minor_version) + packet += FourCC(self.major_brand) + packet += U32BE(self.minor_version) for brand in self.compatible_brands: - packet.write_padded(brand, 4) + packet += FourCC(brand) @classmethod def _deserialize(cls, io): - major_brand = io.read_padded(4) - minor_version = io.read_u32() + major_brand = FourCC.read(io) + minor_version = U32BE.read(io) compatible_brands = [] - while io.data_left > 0: - brand = io.read_padded(4) + while True: + try: + brand = FourCC.read(io) + except IOError: + break + compatible_brands.append(brand) return cls(major_brand, minor_version, @@ -235,54 +221,54 @@ def size(self): return size def _serialize(self, packet): - packet.write_u8(self.version) - packet.write_u24(0) # Reserved + packet += U8(self.version) + packet += U24BE(0) # Reserved - packet.write_u3264(self.version, self.creation_time) - packet.write_u3264(self.version, self.modification_time) - packet.write_u32(self.time_scale) - packet.write_u3264(self.version, self.duration) + packet += U3264(self.creation_time, self.version) + packet += U3264(self.modification_time, self.version) + packet += U32BE(self.time_scale) + packet += U3264(self.duration, self.version) - packet.write_s16_16(self.rate) - packet.write_s8_8(self.volume) + packet += S16BE_16(self.rate) + packet += S8_8BE(self.volume) - packet.write_u16(0) # Reserved - packet.write_u32(0) # Reserved - packet.write_u32(0) # Reserved + packet += U16BE(0) # Reserved + packet += U32BE(0) # Reserved + packet += U32BE(0) # Reserved for m in self.matrix: - packet.write_u32(m) + packet += U32BE(m) for i in range(6): - packet.write_u32(0) # Reserved + packet += U32BE(0) # Reserved - packet.write_u32(self.next_track_id) + packet += U32BE(self.next_track_id) @classmethod def _deserialize(cls, io): - version = io.read_u8() - io.read_u24() # Reserved + version = U8.read(io) + U24BE.read(io) # Reserved - creation_time = io.read_u3264(version) - modification_time = io.read_u3264(version) - time_scale = io.read_u32() - duration = io.read_u3264(version) + creation_time = U3264.read(io, version) + modification_time = U3264.read(io, version) + time_scale = U32BE.read(io) + duration = U3264.read(io, version) - rate = io.read_s16_16() - volume = io.read_s8_8() + rate = S16_16.read(io) + volume = S8_8BE.read(io) - io.read_u16() # Reserved - io.read_u32() # Reserved - io.read_u32() # Reserved + U16BE.read(io) # Reserved + U32BE.read(io) # Reserved + U32BE.read(io) # Reserved matrix = [] for i in range(9): - matrix.append(io.read_u32()) + matrix.append(U32BE.read(io)) for i in range(6): - io.read_u32() # Reserved + U32BE.read(io) # Reserved - next_track_id = io.read_u32() + next_track_id = U32BE.read(io) return cls(version, creation_time, modification_time, time_scale, duration, @@ -320,12 +306,12 @@ def size(self): return 4 def _serialize(self, packet): - packet.write_u32(self.flags.byte) + packet += U32BE(self.flags.byte) @classmethod def _deserialize(cls, io): flags = cls.Flags() - flags.byte = io.read_u32() + flags.byte = U32BE.read(io) return cls(flags.bit.sample_depends_on, flags.bit.sample_is_depended_on, flags.bit.sample_has_redundancy, flags.bit.sample_padding_value, @@ -349,23 +335,23 @@ def size(self): return 1+3+4+4+4+4+self.default_sample_flags.size def _serialize(self, packet): - packet.write_u8(self.version) - packet.write_u24(0) # Reserved - packet.write_u32(self.track_id) - packet.write_u32(self.default_sample_description_index) - packet.write_u32(self.default_sample_duration) - packet.write_u32(self.default_sample_size) + packet += U8(self.version) + packet += U24BE(0) # Reserved + packet += U32BE(self.track_id) + packet += U32BE(self.default_sample_description_index) + packet += U32BE(self.default_sample_duration) + packet += U32BE(self.default_sample_size) self.default_sample_flags.serialize(packet) @classmethod def _deserialize(cls, io): - version = io.read_u8() - flags = io.read_u24() - track_id = io.read_u32() - default_sample_description_index = io.read_u32() - default_sample_duration = io.read_u32() - default_sample_size = io.read_u32() - default_sample_flags = SampleFlags.deserialize(io=io) + version = U8.read(io) + flags = U24BE.read(io) + track_id = U32BE.read(io) + default_sample_description_index = U32BE.read(io) + default_sample_duration = U32BE.read(io) + default_sample_size = U32BE.read(io) + default_sample_flags = SampleFlags.deserialize(io) return cls(version, track_id, default_sample_description_index, @@ -401,54 +387,54 @@ def size(self): return size def _serialize(self, packet): - packet.write_u8(self.version) - packet.write_u24(self.flags) + packet += U8(self.version) + packet += U24BE(self.flags) - packet.write_u3264(self.version, self.creation_time) - packet.write_u3264(self.version, self.modification_time) - packet.write_u32(self.track_id) - packet.write_u32(0) # Reserved - packet.write_u3264(self.version, self.duration) + packet += U3264(self.creation_time, self.version) + packet += U3264(self.modification_time, self.version) + packet += U32BE(self.track_id) + packet += U32BE(0) # Reserved + packet += U3264(self.duration, self.version) for i in range(2): - packet.write_u32(0) # Reserved + packet += U32BE(0) # Reserved - packet.write_s16(self.layer) - packet.write_s16(self.alternate_group) - packet.write_s8_8(self.volume) - packet.write_u16(0) # Reserved + packet += S16BE(self.layer) + packet += S16BE(self.alternate_group) + packet += S8_8BE(self.volume) + packet += U16BE(0) # Reserved for i in range(9): - packet.write_u32(self.transform_matrix[i]) + packet += U32BE(self.transform_matrix[i]) - packet.write_s16_16(self.width) - packet.write_s16_16(self.height) + packet += S16BE_16(self.width) + packet += S16BE_16(self.height) @classmethod def _deserialize(cls, io): - version = io.read_u8() - flags = io.read_u24() + version = U8.read(io) + flags = U24BE.read(io) - creation_time = io.read_u3264(version) - modification_time = io.read_u3264(version) - track_id = io.read_u32() - io.read_u32() # Reserved - duration = io.read_u3264(version) + creation_time = U3264.read(io, version) + modification_time = U3264.read(io, version) + track_id = U32BE.read(io) + U32BE.read(io) # Reserved + duration = U3264.read(io, version) for i in range(2): - io.read_u32() # Reserved + U32BE.read(io) # Reserved - layer = io.read_s16() - alternate_group = io.read_s16() - volume = io.read_s8_8() - io.read_u16() # Reserved + layer = S16BE.read(io) + alternate_group = S16BE.read(io) + volume = S8_8BE.read(io) + U16BE.read(io) # Reserved transform_matrix = [] for i in range(9): - transform_matrix.append(io.read_s32()) + transform_matrix.append(S32BE.read(io)) - width = io.read_s16_16() - height = io.read_s16_16() + width = S16_16.read(io) + height = S16_16.read(io) return cls(version, flags, creation_time, modification_time, track_id, duration, layer, alternate_group, volume, @@ -475,29 +461,29 @@ def size(self): return size def _serialize(self, packet): - packet.write_u8(self.version) - packet.write_u24(0) # Reserved + packet += U8(self.version) + packet += U24BE(0) # Reserved - packet.write_u3264(self.version, self.creation_time) - packet.write_u3264(self.version, self.modification_time) - packet.write_u32(self.time_scale) - packet.write_u3264(self.version, self.duration) + packet += U3264(self.creation_time, self.version) + packet += U3264(self.modification_time, self.version) + packet += U32BE(self.time_scale) + packet += U3264(self.duration, self.version) - packet.write_s16(iso639_to_lang(self.language)) - packet.write_u16(0) # Reserved + packet += S16BE(iso639_to_lang(self.language)) + packet += U16BE(0) # Reserved @classmethod def _deserialize(cls, io): - version = io.read_u8() - io.read_u24() # Reserved + version = U8.read(io) + U24BE.read(io) # Reserved - creation_time = io.read_u3264(version) - modification_time = io.read_u3264(version) - time_scale = io.read_u32() - duration = io.read_u3264(version) + creation_time = U3264.read(io, version) + modification_time = U3264.read(io, version) + time_scale = U32BE.read(io) + duration = U3264.read(io, version) - language = lang_to_iso639(io.read_u16()) - io.read_u16() # Reserved + language = lang_to_iso639(U16BE.read(io)) + U16BE.read(io) # Reserved return cls(version, creation_time, modification_time, time_scale, duration, language) @@ -519,29 +505,29 @@ def size(self): return size def _serialize(self, packet): - packet.write_u8(self.version) - packet.write_u24(0) # Reserved - packet.write_u32(self.predefined) - packet.write_padded(self.handler_type, 4) + packet += U8(self.version) + packet += U24BE(0) # Reserved + packet += U32BE(self.predefined) + packet += FourCC(self.handler_type) for i in range(3): - packet.write_u32(0) # Reserved + packet += U32BE(0) # Reserved - packet.write(bytes(self.name, "utf8")) - #packet.write_string(self.name) + #packet += self.name.encode("utf8", "ignore") + packet += CString(self.name) @classmethod def _deserialize(cls, io): - version = io.read_u8() - flags = io.read_u24() # Reserved + version = U8.read(io) + flags = U24BE.read(io) # Reserved - predefined = io.read_u32() - handler_type = io.read_padded(4) + predefined = U32BE.read(io) + handler_type = FourCC.read(io) for i in range(3): - io.read_u32() # Reserved + U32BE.read(io) # Reserved - name = io.read_string() + name = CString.read(io) return cls(version, predefined, handler_type, name) @@ -559,22 +545,22 @@ def size(self): return 1+3+2+(3*2) def _serialize(self, packet): - packet.write_u8(self.version) - packet.write_u24(self.flags) - packet.write_u16(self.graphics_mode) + packet += U8(self.version) + packet += U24BE(self.flags) + packet += U16BE(self.graphics_mode) for i in range(3): - packet.write_u16(self.op_color[i]) + packet += U16BE(self.op_color[i]) @classmethod def _deserialize(cls, io): - version = io.read_u8() - flags = io.read_u24() + version = U8.read(io) + flags = U24BE.read(io) - graphics_mode = io.read_u16() + graphics_mode = U16BE.read(io) op_color = [] for i in range(3): - op_color.append(io.read_u16()) + op_color.append(U16BE.read(io)) return cls(version, flags, graphics_mode, op_color) @@ -594,22 +580,22 @@ def size(self): return size def _serialize(self, packet): - packet.write_u8(self.version) - packet.write_u24(0) # Reserved - packet.write_u32(len(self.boxes)) + packet += U8(self.version) + packet += U24BE(0) # Reserved + packet += U32BE(len(self.boxes)) for box in self.boxes: box.serialize(packet) @classmethod def _deserialize(cls, io): - version = io.read_u8() - flags = io.read_u24() + version = U8.read(io) + flags = U24BE.read(io) - entry_count = io.read_u32() + entry_count = U32BE.read(io) boxes = [] for i in range(entry_count): - box = Box.deserialize(io=io) + box = Box.deserialize(io) boxes.append(box) return cls(version, boxes) @@ -624,13 +610,13 @@ def size(self): return 4 def _serialize(self, packet): - packet.write_u8(self.version) - packet.write_u24(self.flags) + packet += U8(self.version) + packet += U24BE(self.flags) @classmethod def _deserialize(cls, io): - version = io.read_u8() - flags = io.read_u24() + version = U8.read(io) + flags = U24BE.read(io) return cls(version, flags) @@ -653,22 +639,22 @@ def boxes(self): return self.descriptions def _serialize(self, packet): - packet.write_u8(self.version) - packet.write_u24(0) # Reserved - packet.write_u32(len(self.descriptions)) + packet += U8(self.version) + packet += U24BE(0) # Reserved + packet += U32BE(len(self.descriptions)) for description in self.descriptions: description.serialize(packet) @classmethod def _deserialize(cls, io): - version = io.read_u8() - flags = io.read_u24() - count = io.read_u32() + version = U8.read(io) + flags = U24BE.read(io) + count = U32BE.read(io) descriptions = [] for i in range(count): - box = Box.deserialize(io=io) + box = Box.deserialize(io) descriptions.append(box) return cls(version, descriptions) @@ -692,13 +678,13 @@ def size(self): return 4 def _serialize(self, packet): - packet.write_u8(self.version) - packet.write_u24(self.flags) + packet += U8(self.version) + packet += U24BE(self.flags) @classmethod def _deserialize(cls, io): for i in range(4): - io.read_u8() + U8.read(io) return cls(version, flags) @@ -783,75 +769,75 @@ def size(self): return size def _serialize(self, packet): - packet.write_u8(self.version) - packet.write_u24(0) # Reserved - packet.write_u32(self.bootstrap_info_version) - packet.write_u8(self.flags.byte) - packet.write_u32(self.time_scale) - packet.write_u64(self.current_media_time) - packet.write_u64(self.smpte_time_code_offset) - packet.write_string(self.movie_identifier) - - packet.write_u8(len(self.server_entry_table)) + packet += U8(self.version) + packet += U24BE(0) # Reserved + packet += U32BE(self.bootstrap_info_version) + packet += U8(self.flags.byte) + packet += U32BE(self.time_scale) + packet += U64BE(self.current_media_time) + packet += U64BE(self.smpte_time_code_offset) + packet += CString(self.movie_identifier) + + packet += U8(len(self.server_entry_table)) for server_entry in self.server_entry_table: - packet.write_string(server_entry) + packet += CString(server_entry) - packet.write_u8(len(self.quality_entry_table)) + packet += U8(len(self.quality_entry_table)) for quality_entry in self.quality_entry_table: - packet.write_string(quality_entry) + packet += CString(quality_entry) - packet.write_string(self.drm_data) - packet.write_string(self.metadata) + packet += CString(self.drm_data) + packet += CString(self.metadata) - packet.write_u8(len(self.segment_run_table_entries)) + packet += U8(len(self.segment_run_table_entries)) for segment_run_table in self.segment_run_table_entries: segment_run_table.serialize(packet) - packet.write_u8(len(self.fragment_run_table_entries)) + packet += U8(len(self.fragment_run_table_entries)) for fragment_run_table in self.fragment_run_table_entries: fragment_run_table.serialize(packet) @classmethod def _deserialize(cls, io): - version = io.read_u8() - io.read_u24() # Reserved - bootstrap_info_version = io.read_u32() + version = U8.read(io) + U24BE.read(io) # Reserved + bootstrap_info_version = U32BE.read(io) flags = cls.Flags() - flags.byte = io.read_u8() - time_scale = io.read_u32() - current_media_time = io.read_u64() - smpte_time_code_offset = io.read_u64() - movie_identifier = io.read_string() + flags.byte = U8.read(io) + time_scale = U32BE.read(io) + current_media_time = U64BE.read(io) + smpte_time_code_offset = U64BE.read(io) + movie_identifier = CString.read(io) server_entry_table = [] - server_entry_count = io.read_u8() + server_entry_count = U8.read(io) for i in range(server_entry_count): - server_entry = io.read_string() + server_entry = CString.read(io) server_entry_table.append(server) quality_entry_table = [] - quality_entry_count = io.read_u8() + quality_entry_count = U8.read(io) for i in range(quality_entry_count): - quality_entry = io.read_string() + quality_entry = CString.read(io) quality_entry_table.append(quality) - drm_data = io.read_string() - metadata = io.read_string() + drm_data = CString.read(io) + metadata = CString.read(io) segment_run_table_entries = [] - segment_run_table_count = io.read_u8() + segment_run_table_count = U8.read(io) for i in range(segment_run_table_count): - segment_run_table = Box.deserialize(io=io) + segment_run_table = Box.deserialize(io) segment_run_table_entries.append(segment_run_table) fragment_run_table_entries = [] - fragment_run_table_count = io.read_u8() + fragment_run_table_count = U8.read(io) for i in range(fragment_run_table_count): - fragment_run_table = Box.deserialize(io=io) + fragment_run_table = Box.deserialize(io) fragment_run_table_entries.append(fragment_run_table) return cls(version, bootstrap_info_version, flags.bit.profile, @@ -871,13 +857,13 @@ def size(self): return 8 def _serialize(self, packet): - packet.write_u32(self.first_segment) - packet.write_u32(self.fragments_per_segment) + packet += U32BE(self.first_segment) + packet += U32BE(self.fragments_per_segment) @classmethod def _deserialize(cls, io): - first_segment = io.read_u32() - fragments_per_segment = io.read_u32() + first_segment = U32BE.read(io) + fragments_per_segment = U32BE.read(io) return cls(first_segment, fragments_per_segment) @@ -903,34 +889,34 @@ def size(self): return size def _serialize(self, packet): - packet.write_u8(self.version) - packet.write_u24(self.flags) - packet.write_u8(len(self.quality_segment_url_modifiers)) + packet += U8(self.version) + packet += U24BE(self.flags) + packet += U8(len(self.quality_segment_url_modifiers)) for quality in self.quality_segment_url_modifiers: - packet.write_string(quality) + packet += CString(quality) - packet.write_u32(len(self.segment_run_entry_table)) + packet += U32BE(len(self.segment_run_entry_table)) for segment_run_entry in self.segment_run_entry_table: segment_run_entry.serialize(packet) @classmethod def _deserialize(cls, io): - version = io.read_u8() - flags = io.read_u24() + version = U8.read(io) + flags = U24BE.read(io) quality_segment_url_modifiers = [] - quality_entry_count = io.read_u8() + quality_entry_count = U8.read(io) for i in range(quality_entry_count): - quality = io.read_string() + quality = CString.read(io) quality_segment_url_modifiers.append(quality) - segment_run_entry_count = io.read_u32() + segment_run_entry_count = U32BE.read(io) segment_run_entry_table = [] for i in range(segment_run_entry_count): - segment_run_entry = SegmentRunEntry.deserialize(io=io) + segment_run_entry = SegmentRunEntry.deserialize(io) segment_run_entry_table.append(segment_run_entry) return cls(version, flags, quality_segment_url_modifiers, @@ -955,21 +941,21 @@ def size(self): return size def _serialize(self, packet): - packet.write_u32(self.first_fragment) - packet.write_u64(self.first_fragment_timestamp) - packet.write_u32(self.fragment_duration) + packet += U32BE(self.first_fragment) + packet += U64BE(self.first_fragment_timestamp) + packet += U32BE(self.fragment_duration) if self.fragment_duration == 0: - packet.write_u8(self.discontinuity_indicator) + packet += U8(self.discontinuity_indicator) @classmethod def _deserialize(cls, io): - first_fragment = io.read_u32() - first_fragment_timestamp = io.read_u64() - fragment_duration = io.read_u32() + first_fragment = U32BE.read(io) + first_fragment_timestamp = U64BE.read(io) + fragment_duration = U32BE.read(io) if fragment_duration == 0: - discontinuity_indicator = io.read_u8() + discontinuity_indicator = U8.read(io) else: discontinuity_indicator = None @@ -1000,36 +986,36 @@ def size(self): return size def _serialize(self, packet): - packet.write_u8(self.version) - packet.write_u24(self.flags) - packet.write_u32(self.time_scale) - packet.write_u8(len(self.quality_segment_url_modifiers)) + packet += U8(self.version) + packet += U24BE(self.flags) + packet += U32BE(self.time_scale) + packet += U8(len(self.quality_segment_url_modifiers)) for quality in self.quality_segment_url_modifiers: - packet.write_string(quality) + packet += CString(quality) - packet.write_u32(len(self.fragment_run_entry_table)) + packet += U32BE(len(self.fragment_run_entry_table)) for fragment_run_entry in self.fragment_run_entry_table: fragment_run_entry.serialize(packet) @classmethod def _deserialize(cls, io): - version = io.read_u8() - flags = io.read_u24() - time_scale = io.read_u32() + version = U8.read(io) + flags = U24BE.read(io) + time_scale = U32BE.read(io) quality_segment_url_modifiers = [] - quality_entry_count = io.read_u8() + quality_entry_count = U8.read(io) for i in range(quality_entry_count): - quality = io.read_string() + quality = CString.read(io) quality_segment_url_modifiers.append(quality) - fragment_run_entry_count = io.read_u32() + fragment_run_entry_count = U32BE.read(io) fragment_run_entry_table = [] for i in range(fragment_run_entry_count): - fragment_run_entry = FragmentRunEntry.deserialize(io=io) + fragment_run_entry = FragmentRunEntry.deserialize(io) fragment_run_entry_table.append(fragment_run_entry) return cls(version, flags, time_scale, diff --git a/src/livestreamer/packages/flashmedia/compat.py b/src/livestreamer/packages/flashmedia/compat.py index 739e2860..691c59a2 100644 --- a/src/livestreamer/packages/flashmedia/compat.py +++ b/src/livestreamer/packages/flashmedia/compat.py @@ -8,6 +8,7 @@ if is_py2: _str = str str = unicode + range = xrange def bytes(b=None, enc="ascii"): if b is None: @@ -17,12 +18,10 @@ def bytes(b=None, enc="ascii"): else: return _str(b) - from StringIO import StringIO as BytesIO - elif is_py3: bytes = bytes str = str - from io import BytesIO + range = range try: @@ -30,4 +29,5 @@ def bytes(b=None, enc="ascii"): except ImportError: from .ordereddict import OrderedDict -__all__ = ["is_py2", "is_py3", "is_win32", "str", "bytes", "BytesIO", "OrderedDict"] +__all__ = ["is_py2", "is_py3", "is_win32", "str", "bytes", "range", + "OrderedDict"] diff --git a/src/livestreamer/packages/flashmedia/f4v.py b/src/livestreamer/packages/flashmedia/f4v.py index 8c967eaa..fb5a38d8 100644 --- a/src/livestreamer/packages/flashmedia/f4v.py +++ b/src/livestreamer/packages/flashmedia/f4v.py @@ -4,30 +4,22 @@ from .compat import is_py2 class F4V(object): - def __init__(self, fd, strict=False, preload=True): + def __init__(self, fd, strict=False, raw_payload=False): self.fd = fd - self.prev_box = None - self.preload = preload + self.raw_payload = raw_payload self.strict = strict def __iter__(self): return self def __next__(self): - # Consume previous box payload if needed - if not self.preload and self.prev_box and \ - isinstance(self.prev_box.payload, RawPayload): - self.prev_box.payload.read() - try: box = Box.deserialize(self.fd, strict=self.strict, - preload=self.preload) + raw_payload=self.raw_payload) except IOError: raise StopIteration - self.prev_box = box - return box if is_py2: diff --git a/src/livestreamer/packages/flashmedia/packet.py b/src/livestreamer/packages/flashmedia/packet.py index b4921de3..78e86eb9 100644 --- a/src/livestreamer/packages/flashmedia/packet.py +++ b/src/livestreamer/packages/flashmedia/packet.py @@ -1,516 +1,46 @@ #!/usr/bin/env python -import struct - -from struct import Struct - -from .compat import * -from .util import * - - - -class ScriptData: - NUMBER, BOOLEAN, STRING, OBJECT, RESERVED, NULL, \ - UNDEFINED, REFERENCE, ECMAARRAY, OBJECTEND, STRICTARRAY, \ - DATE, LONGSTRING = range(13) - - class Object(OrderedDict): - pass - - class ECMAArray(OrderedDict): - pass - - class ObjectEnd(IOError): - pass - - class Date(object): - def __init__(self, timestamp, offset): - self.timestamp = timestamp - self.offset = offset - class Packet(object): @classmethod - def _deserialize(cls): + def _deserialize(cls, fd): raise NotImplementedError @classmethod - def deserialize(cls, fd=None, io=None, **kw): - if not io: - if not fd: - raise IOError("Missing fd parameter") + def _deserialize_from(cls, buf, offset, **kw): + raise NotImplementedError - io = PacketIO(fd) + @classmethod + def deserialize(cls, fd, **kw): + return cls._deserialize(fd, **kw) - return cls._deserialize(io, **kw) + @classmethod + def deserialize_from(cls, buf, offset, **kw): + return cls._deserialize_from(buf, offset, **kw) def _serialize(self): raise NotImplementedError + def _serialize_into(self, buf, offset): + raise NotImplementedError + def serialize(self, packet=None, **kw): - if not packet: - packet = PacketIO() + if packet is None: + packet = bytearray() self._serialize(packet, **kw) - return packet.getvalue() - - def __bytes__(self): - return self.serialize() - -# __str__ = __bytes__ - - -class PacketIO(object): - class BigEndian: - U8 = Struct(">B") - U16 = Struct(">H") - S16 = Struct(">h") - U24 = Struct(">BH") - S24 = Struct(">Bh") - U32 = Struct(">I") - S32 = Struct(">i") - U64 = Struct(">Q") - Double = Struct(">d") - - class LittleEndian: - U8 = Struct(" 4: - raise IOError - - combined = byte(low_high[3]) + low_high[:3] - - try: - ret = self.struct.S32.unpack(combined)[0] - except struct.error: - raise IOError - - return ret - - def read_u3264(self, version): - if version == 1: - return self.read_u64() - else: - return self.read_u32() - - def read_u64(self): - try: - ret = self.struct.U64.unpack(self.read(8))[0] - except struct.error: - raise IOError - - return ret + return packet - def read_double(self): - try: - ret = self.struct.Double.unpack(self.read(8))[0] - except struct.error: - raise IOError + def serialize2(self): + buf = bytearray(self.size) + self.serialize_into(buf, 0) + return buf - return ret - - def read_string(self): - ret = b"" - - while True: - try: - ch = self.read(1) - except IOError: - break - - if len(ch) == 0: - break - - if ord(ch) == 0: - break - - ret += ch - - return str(ret, "utf8") - - def read_padded(self, length): - return str(self.read(length), "ascii").rstrip() - - # ScriptData values - - def read_script_value(self): - typ = self.read_u8() - - if typ == ScriptData.NUMBER: - return self.read_script_number() - - elif typ == ScriptData.BOOLEAN: - return self.read_script_boolean() - - elif typ == ScriptData.STRING: - return self.read_script_string() - - elif typ == ScriptData.OBJECT: - return self.read_script_object(ScriptData.Object()) - - elif typ == ScriptData.NULL or typ == ScriptData.UNDEFINED: - return None - - elif typ == ScriptData.REFERENCE: - ref = self.read_u16() - - return self._objects[ref] - - elif typ == ScriptData.ECMAARRAY: - container = ScriptData.ECMAArray() - container.length = self.read_u32() - - return self.read_script_object(container) - - elif typ == ScriptData.OBJECTEND: - raise ScriptData.ObjectEnd - - elif typ == ScriptData.STRICTARRAY: - length = self.read_u32() - rval = [] - - for i in range(length): - val = self.read_script_value() - rval.append(val) - - return rval - - elif typ == ScriptData.DATE: - date = self.read_double() - offset = self.read_s16() - - return ScriptData.Date(date, offset) - - elif typ == ScriptData.LONGSTRING: - return self.read_script_string(True) - - raise IOError("Unhandled script data type: %d" % typ) - - def read_script_number(self): - return self.read_double() - - def read_script_boolean(self): - val = self.read_u8() - - return bool(val) - - def read_script_string(self, full=False): - if full: - length = self.read_u32() - else: - length = self.read_u16() - - val = self.read(length) - - return val.decode("utf8", "ignore") - - def read_script_object(self, container): - while True: - try: - key = self.read_script_string() - value = self.read_script_value() - except IOError: - break - - if len(key) == 0: - break - - container[key] = value - - self._objects.append(container) - - return container - - def write_script_value(self, val): - if isinstance(val, bool): - self.write_u8(ScriptData.BOOLEAN) - self.write_script_boolean(val) - - elif isinstance(val, int) or isinstance(val, float): - self.write_u8(ScriptData.NUMBER) - self.write_script_number(val) - - elif isstring(val): - if len(val) > 65535: - self.write_u8(ScriptData.LONGSTRING) - else: - self.write_u8(ScriptData.STRING) - - self.write_script_string(val) - - elif isinstance(val, list): - self.write_u8(ScriptData.STRICTARRAY) - self.write_u32(len(val)) - - for value in val: - self.write_script_value(value) - - elif isinstance(val, ScriptData.Object): - self.write_u8(ScriptData.OBJECT) - self.write_script_object(val) - - elif isinstance(val, ScriptData.ECMAArray): - self.write_u8(ScriptData.ECMAARRAY) - self.write_u32(len(val)) - self.write_script_object(val) - - elif isinstance(val, ScriptData.Date): - self.write_u8(ScriptData.DATE) - self.write_double(val.timestamp) - self.write_s16(val.offset) - - else: - raise IOError("Cannot convert {0} to ScriptData value").format(type(val).__name__) - - - def write_script_number(self, val): - self.write_double(float(val)) - - def write_script_boolean(self, val): - self.write_u8(int(val)) - - def write_script_string(self, val): - length = len(val) - - if length > 65535: - self.write_u32(length) - else: - self.write_u16(length) - - self.write(bytes(val, "utf8")) - - def write_script_object(self, val): - for key, value in val.items(): - self.write_script_string(key) - self.write_script_value(value) - - # Empty string + marker ends object - self.write_script_string("") - self.write_u8(ScriptData.OBJECTEND) - - @classmethod - def script_value_size(cls, val): - size = 1 - - if isinstance(val, bool): - size += 1 - elif isinstance(val, int) or isinstance(val, float): - size += 8 - elif isstring(val): - size += cls.script_string_size(val) - - elif isinstance(val, list): - size += 4 - - for value in val: - size += cls.script_value_size(value) - - elif isinstance(val, ScriptData.Object): - size += cls.script_object_size(val) - - elif isinstance(val, ScriptData.ECMAArray): - size += 4 - size += cls.script_object_size(val) - - elif isinstance(val, ScriptData.Date): - size += 10 - - return size - - @classmethod - def script_string_size(cls, val): - size = len(val) - - if size > 65535: - size += 4 - else: - size += 2 - - return size - - @classmethod - def script_object_size(cls, val): - size = 3 - - for key, value in val.items(): - size += cls.script_string_size(key) - size += cls.script_value_size(value) - - return size + def serialize_into(self, buf, offset): + return self._serialize_into(buf, offset) + def __bytes__(self): + return self.serialize() class TagData(Packet): @property @@ -520,5 +50,4 @@ def size(self): else: return len(self.data) - -__all__ = ["Packet", "PacketIO", "TagData", "ScriptData"] +__all__ = ["Packet", "TagData"] diff --git a/src/livestreamer/packages/flashmedia/tag.py b/src/livestreamer/packages/flashmedia/tag.py index c755f35c..63e7d980 100644 --- a/src/livestreamer/packages/flashmedia/tag.py +++ b/src/livestreamer/packages/flashmedia/tag.py @@ -1,16 +1,64 @@ #!/usr/bin/env python from ctypes import BigEndianStructure, Union, c_uint8 +from io import BytesIO from .compat import * from .error import * from .packet import * +from .types import * from .util import * TAG_TYPE_AUDIO = 8 TAG_TYPE_VIDEO = 9 TAG_TYPE_SCRIPT = 18 +AUDIO_CODEC_ID_PCM = 0 +AUDIO_CODEC_ID_ADPCM = 1 +AUDIO_CODEC_ID_MP3 = 2 +AUDIO_CODEC_ID_PCM_LE = 3 +AUDIO_CODEC_ID_NELLYMOSSER_16 = 4 +AUDIO_CODEC_ID_NELLYMOSSER_8 = 5 +AUDIO_CODEC_ID_NELLYMOSSER = 6 +AUDIO_CODEC_ID_G711_A = 7 +AUDIO_CODEC_ID_G711_MU = 8 +AUDIO_CODEC_ID_AAC = 10 +AUDIO_CODEC_ID_SPEEX = 11 +AUDIO_CODEC_ID_MP3_8 = 14 +AUDIO_CODEC_ID_DEVICE = 15 + +AUDIO_RATE_5_5_KHZ = 0 +AUDIO_RATE_11_KHZ = 1 +AUDIO_RATE_22_KHZ = 2 +AUDIO_RATE_44_KHZ = 3 + +AUDIO_BIT_RATE_8 = 0 +AUDIO_BIT_RATE_16 = 1 + +AUDIO_TYPE_MONO = 0 +AUDIO_TYPE_STEREO = 1 + +AAC_PACKET_TYPE_SEQUENCE_HEADER = 0 +AAC_PACKET_TYPE_RAW = 1 + +VIDEO_FRAME_TYPE_KEY_FRAME = 1 +VIDEO_FRAME_TYPE_INTER_FRAME = 2 +VIDEO_FRAME_TYPE_DIS_INTER_FRAME = 3 +VIDEO_FRAME_TYPE_GEN_KEY_FRAME = 4 +VIDEO_FRAME_TYPE_COMMAND_FRAME = 5 + +VIDEO_CODEC_ID_H263 = 2 +VIDEO_CODEC_ID_SCREEN_VIDEO = 3 +VIDEO_CODEC_ID_VP6 = 4 +VIDEO_CODEC_ID_VP6A = 5 +VIDEO_CODEC_ID_SCREEN_VIDEO_2 = 6 +VIDEO_CODEC_ID_AVC = 7 + +AVC_PACKET_TYPE_SEQUENCE_HEADER = 0 +AVC_PACKET_TYPE_NALU = 1 +AVC_PACKET_TYPE_END_OF_SEQUENCE = 2 + + class TypeFlags(Union): class Bits(BigEndianStructure): _fields_ = [("rsv1", c_uint8, 5), @@ -62,6 +110,9 @@ def __repr__(self): return reprformat.format(version=self.version, offset=self.data_offset, has_audio=self.has_audio, has_video=self.has_video) + @property + def size(self): + return 13 has_audio = flagproperty("flags", "audio", True) has_video = flagproperty("flags", "video", True) @@ -73,31 +124,66 @@ def _deserialize(cls, io): if head != b"FLV": raise FLVError("Invalid FLV header") - version = io.read_u8() + version = U8.read(io) flags = TypeFlags() - flags.byte = io.read_u8() - offset = io.read_u32() - tag0_size = io.read_u32() + flags.byte = U8.read(io) + offset = U32BE.read(io) + tag0_size = U32BE.read(io) return Header(version, bool(flags.bit.audio), bool(flags.bit.video), offset, tag0_size) + @classmethod + def _deserialize_from(cls, buf, offset): + head = buf[offset:offset + 3] + offset += 3 + + if head != b"FLV": + raise FLVError("Invalid FLV header") + + flags = TypeFlags() + + (version, flags.byte, tag0_offset, + tag0_size) = unpack_many_from(buf, offset, (U8, U8, U32BE, U32BE)) + + rval = Header(version, bool(flags.bit.audio), bool(flags.bit.video), + tag0_offset, tag0_size) + + offset += 10 + + return (rval, offset) + def _serialize(self, packet): - packet.write(b"FLV") - packet.write_u8(self.version) - packet.write_u8(self.flags.byte) - packet.write_u32(self.data_offset) - packet.write_u32(self.tag0_size) + packet += b"FLV" + packet += U8(self.version) + packet += U8(self.flags.byte) + packet += U32BE(self.data_offset) + packet += U32BE(self.tag0_size) + + def _serialize_into(self, packet, offset): + offset = pack_bytes_into(packet, offset, b"FLV") + offset = pack_many_into(packet, offset, + (U8, U8, U32BE, U32BE), + (self.version, self.flags.byte, + self.data_offset, self.tag0_size)) + + return offset class Tag(Packet): def __init__(self, typ=TAG_TYPE_SCRIPT, timestamp=0, data=None, - streamid=0, filter=False, padding=b""): + streamid=0, filter=False, padding=None): self.flags = TagFlags() self.flags.bit.rsv = 0 self.flags.bit.type = typ self.flags.bit.filter = int(filter) + if not data: + data = RawData() + + if not padding: + padding = b"" + self.data = data self.streamid = streamid self.timestamp = timestamp @@ -120,54 +206,125 @@ def data_size(self): def tag_size(self): return 11 + self.data_size + len(self.padding) + @property + def size(self): + return 4 + self.tag_size + @classmethod - def _deserialize(cls, io, strict=False): - flags = TagFlags() - flags.byte = io.read_u8() - data_size = io.read_u24() - timestamp = io.read_s32e() - streamid = io.read_u24() + def _deserialize(cls, io, strict=False, raw_data=False): + header = io.read(11) + + if len(header) < 11: + raise FLVError("Insufficient tag header") + + (flagb, data_size, timestamp, timestamp_ext, + streamid) = unpack_many_from(header, 0, (U8, U24BE, U24BE, U8, U24BE)) + + flags = TagFlags() + flags.byte = flagb + timestamp |= timestamp_ext << 24 + # Don't parse encrypted data if flags.bit.filter == 1: - raise FLVError("Encrypted tags are not supported") + raw_data = True if flags.bit.type in TagDataTypes: datacls = TagDataTypes[flags.bit.type] else: raise FLVError("Unknown tag type!") - if data_size > 0: - io.data_left = data_size - data = datacls.deserialize(io=io) - padding = io.read() - io.data_left = None + tag_data = chunked_read(io, data_size, exception=FLVError) + + if data_size > 0 and not raw_data: + tag_data_io = BytesIO(tag_data) + data = datacls.deserialize(tag_data_io) + padding = tag_data_io.read() else: - data = RawData() + data = RawData(tag_data) padding = b"" tag = Tag(flags.bit.type, timestamp, data, streamid, bool(flags.bit.filter), padding) - tag_size = io.read_u32() + tag_size = U32BE.read(io) if strict and tag.tag_size != tag_size: raise FLVError("Data size mismatch when deserialising tag") return tag + @classmethod + def _deserialize_from(cls, buf, offset, strict=False, + raw_data=False): + (flagb, data_size, timestamp, timestamp_ext, + streamid) = unpack_many_from(buf, offset, (U8, U24BE, U24BE, U8, U24BE)) + + offset += 11 + + flags = TagFlags() + flags.byte = flagb + timestamp |= timestamp_ext << 24 + + # Don't parse encrypted data + if flags.bit.filter == 1: + raw_data = True + + if flags.bit.type in TagDataTypes: + datacls = TagDataTypes[flags.bit.type] + else: + raise FLVError("Unknown tag type!") + + if data_size > 0 and not raw_data: + data, doffset = datacls.deserialize_from(buf, offset, buf_size=data_size) + padding = buf[doffset:offset + data_size] + else: + data = RawData(buf[offset:offset + data_size]) + padding = b"" + + offset += data_size + + tag = Tag(flags.bit.type, timestamp, data, + streamid, bool(flags.bit.filter), padding) + + tag_size = U32BE.unpack_from(buf, offset)[0] + offset += U32BE.size + + if strict and tag.tag_size != tag_size: + raise FLVError("Data size mismatch when deserialising tag") + + return (tag, offset) + def _serialize(self, packet, strict=True): - packet.write_u8(self.flags.byte) - packet.write_u24(self.data_size) - packet.write_s32e(self.timestamp) - packet.write_u24(self.streamid) + packet += U8(self.flags.byte) + packet += U24BE(self.data_size) + + packet += U24BE(self.timestamp & 0xFFFFFF) + packet += U8((self.timestamp >> 24) & 0x7F) + packet += U24BE(self.streamid) self.data.serialize(packet) - packet.write(self.padding) + packet += self.padding - if strict and self.tag_size != packet.written: + if strict and self.tag_size != len(packet): raise FLVError("Data size mismatch when serialising tag") - packet.write_u32(packet.written) + packet += U32BE(self.tag_size) + + def _serialize_into(self, packet, offset): + offset = pack_many_into(packet, offset, + (U8, U24BE, U24BE, U8, U24BE), + (self.flags.byte, self.data_size, + self.timestamp & 0xFFFFFF, + (self.timestamp >> 24) & 0x7F, + self.streamid)) + + offset = self.data.serialize_into(packet, offset) + offset = pack_bytes_into(packet, offset, self.padding) + + U32BE.pack_into(packet, offset, self.tag_size) + offset += 4 + + return offset class FrameData(TagData): @@ -190,18 +347,42 @@ def size(self): @classmethod def _deserialize(cls, io): - typ = io.read_u8() + typ = U8.read(io) data = io.read() return cls(typ, data) + @classmethod + def _deserialize_from(cls, buf, offset, buf_size=None): + if not buf_size: + buf_size = len(buf) + + typ = U8.unpack_from(buf, offset)[0] + offset += U8.size + buf_size -= U8.size + + data = buf[offset:offset + buf_size] + offset += buf_size + + return (cls(typ, data), offset) + def _serialize(self, packet): - packet.write_u8(self.type) - packet.write(self.data) + packet += U8(self.type) + packet += self.data + + def _serialize_into(self, packet, offset): + U8.pack_into(packet, offset, self.type) + offset += U8.size + offset = pack_bytes_into(packet, offset, self.data) + + return offset class RawData(TagData): - def __init__(self, data=b""): + def __init__(self, data=None): + if not data: + data = b"" + self.data = data def __repr__(self): @@ -209,10 +390,25 @@ def __repr__(self): @classmethod def _deserialize(cls, io): - return cls() + return cls(io.read()) + + @classmethod + def _deserialize_from(cls, buf, offset, buf_size=None): + if not data_size: + buf_size = len(buf) + + data = buf[offset:offset + buf_size] + rval = cls(data) + offset += len(data) + + return (rval, offset) def _serialize(self, packet): - packet.write(self.data) + packet += self.data + + def _serialize_into(self, packet, offset): + return pack_bytes_into(packet, offset, self.data) + class AudioData(TagData): def __init__(self, codec=0, rate=0, bits=0, type=0, data=None): @@ -245,9 +441,9 @@ def size(self): @classmethod def _deserialize(cls, io): flags = AudioFlags() - flags.byte = io.read_u8() + flags.byte = U8.read(io) - if flags.bit.codec == 10: + if flags.bit.codec == AUDIO_CODEC_ID_AAC: data = AACAudioData.deserialize(io) else: data = io.read() @@ -255,13 +451,46 @@ def _deserialize(cls, io): return cls(flags.bit.codec, flags.bit.rate, flags.bit.bits, flags.bit.type, data) + @classmethod + def _deserialize_from(cls, buf, offset, buf_size=None): + if not buf_size: + buf_size = len(buf) + + flags = AudioFlags() + flags.byte = U8.unpack_from(buf, offset)[0] + offset += U8.size + buf_size -= U8.size + + if flags.bit.codec == 10: + data, offset = AACAudioData.deserialize_from(buf, offset, + buf_size=buf_size) + else: + data = buf[offset:offset + data_size] + offset += data_size + + obj = cls(flags.bit.codec, flags.bit.rate, flags.bit.bits, + flags.bit.type, data) + + return (obj, offset) + def _serialize(self, packet): - packet.write_u8(self.flags.byte) + packet += U8(self.flags.byte) if isinstance(self.data, Packet): self.data.serialize(packet) else: - packet.write(self.data) + packet += self.data + + def _serialize_into(self, packet, offset): + U8.pack_into(packet, offset, self.flags.byte) + offset += 1 + + if isinstance(self.data, Packet): + offset = self.data.serialize_into(packet, offset) + else: + offset = pack_bytes_into(packet, offset, self.data) + + return offset class AACAudioData(FrameData): @@ -269,10 +498,14 @@ class AACAudioData(FrameData): class VideoData(TagData): - def __init__(self, type=0, codec=0, data=b""): + def __init__(self, type=0, codec=0, data=None): self.flags = VideoFlags() self.flags.bit.type = type self.flags.bit.codec = codec + + if not data: + data = b"" + self.data = data def __repr__(self): @@ -294,25 +527,61 @@ def size(self): @classmethod def _deserialize(cls, io): flags = VideoFlags() - flags.byte = io.read_u8() + flags.byte = U8.read(io) - if flags.bit.type == 5: + if flags.bit.type == VIDEO_FRAME_TYPE_COMMAND_FRAME: data = VideoCommandFrame.deserialize(io) else: - if flags.bit.codec == 7: + if flags.bit.codec == VIDEO_CODEC_ID_AVC: data = AVCVideoData.deserialize(io) else: data = io.read() return cls(flags.bit.type, flags.bit.codec, data) + @classmethod + def _deserialize_from(cls, buf, offset, buf_size=None): + if not buf_size: + buf_size = len(buf) + + flags = VideoFlags() + flags.byte = U8.unpack_from(buf, offset)[0] + offset += U8.size + buf_size -= U8.size + + if flags.bit.type == VIDEO_FRAME_TYPE_COMMAND_FRAME: + data, offset = VideoCommandFrame.deserialize_from(buf, offset, + buf_size=buf_size) + else: + if flags.bit.codec == VIDEO_CODEC_ID_AVC: + data, offset = AVCVideoData.deserialize_from(buf, offset, + buf_size=buf_size) + else: + data = buf[offset:offset + buf_size] + offset += buf_size + + obj = cls(flags.bit.type, flags.bit.codec, data) + + return (obj, offset) + def _serialize(self, packet): - packet.write_u8(self.flags.byte) + packet += U8(self.flags.byte) if isinstance(self.data, Packet): self.data.serialize(packet) else: - packet.write(self.data) + packet += self.data + + def _serialize_into(self, packet, offset): + U8.pack_into(packet, offset, self.flags.byte) + offset += 1 + + if isinstance(self.data, Packet): + offset = self.data.serialize_into(packet, offset) + else: + offset = pack_bytes_into(packet, offset, self.data) + + return offset class VideoCommandFrame(FrameData): @@ -320,9 +589,13 @@ class VideoCommandFrame(FrameData): class AVCVideoData(TagData): - def __init__(self, type=1, composition_time=0, data=b""): + def __init__(self, type=1, composition_time=0, data=None): self.type = type self.composition_time = composition_time + + if not data: + data = b"" + self.data = data def __repr__(self): @@ -341,16 +614,45 @@ def size(self): @classmethod def _deserialize(cls, io): - typ = io.read_u8() - composition_time = io.read_s24() + typ = U8.read(io) + composition_time = S24BE.read(io) data = io.read() return cls(typ, composition_time, data) + @classmethod + def _deserialize_from(cls, buf, offset, buf_size=None): + if not buf_size: + buf_size = None + + typ = U8.unpack_from(buf, offset)[0] + offset += U8.size + + composition_time = S24BE.unpack_from(buf, offset)[0] + offset += S24BE.size + + buf_size -= U8.size + S24BE.size + data = buf[offset:offset + buf_size] + offset += len(data) + + obj = cls(typ, composition_time, data) + + return (obj, offset) + def _serialize(self, packet): - packet.write_u8(self.type) - packet.write_s24(self.composition_time) - packet.write(self.data) + packet += U8(self.type) + packet += S24BE(self.composition_time) + packet += self.data + + def _serialize_into(self, packet, offset): + offset = pack_many_into(packet, offset, + (U8, S24BE), + (self.type, self.composition_time)) + + offset = pack_bytes_into(packet, offset, self.data) + + return offset + class ScriptData(TagData): @@ -364,22 +666,34 @@ def __repr__(self): @property def size(self): - size = PacketIO.script_value_size(self.name) - size += PacketIO.script_value_size(self.value) + size = ScriptDataValue.size(self.name) + size += ScriptDataValue.size(self.value) return size @classmethod def _deserialize(cls, io): - io._objects = [] - name = io.read_script_value() - value = io.read_script_value() + name = ScriptDataValue.read(io) + value = ScriptDataValue.read(io) return ScriptData(name, value) + @classmethod + def _deserialize_from(cls, buf, offset, buf_size=None): + name, offset = ScriptDataValue.unpack_from(buf, offset) + value, offset = ScriptDataValue.unpack_from(buf, offset) + + return (ScriptData(name, value), offset) + def _serialize(self, packet): - packet.write_script_value(self.name) - packet.write_script_value(self.value) + packet += ScriptDataValue.pack(self.name) + packet += ScriptDataValue.pack(self.value) + + def _serialize_into(self, packet, offset): + offset = ScriptDataValue.pack_into(packet, offset, self.name) + offset = ScriptDataValue.pack_into(packet, offset, self.value) + + return offset TagDataTypes = { diff --git a/src/livestreamer/packages/flashmedia/types.py b/src/livestreamer/packages/flashmedia/types.py new file mode 100644 index 00000000..977c568c --- /dev/null +++ b/src/livestreamer/packages/flashmedia/types.py @@ -0,0 +1,848 @@ +from .compat import * +from .util import isstring, pack_bytes_into + +from collections import namedtuple +from struct import Struct, error as struct_error + +(SCRIPT_DATA_TYPE_NUMBER, SCRIPT_DATA_TYPE_BOOLEAN, + SCRIPT_DATA_TYPE_STRING, SCRIPT_DATA_TYPE_OBJECT, + SCRIPT_DATA_TYPE_RESERVED, SCRIPT_DATA_TYPE_NULL, + SCRIPT_DATA_TYPE_UNDEFINED, SCRIPT_DATA_TYPE_REFERENCE, + SCRIPT_DATA_TYPE_ECMAARRAY, SCRIPT_DATA_TYPE_OBJECTEND, + SCRIPT_DATA_TYPE_STRICTARRAY, SCRIPT_DATA_TYPE_DATE, + SCRIPT_DATA_TYPE_LONGSTRING) = range(13) + +class PrimitiveType(Struct): + def __call__(self, *args): + return self.pack(*args) + + def read(self, fd): + data = fd.read(self.size) + + if len(data) != self.size: + raise IOError("Unable to read required amount of data") + + return self.unpack(data)[0] + +class PrimitiveClassType(PrimitiveType): + def __init__(self, format, cls): + self.cls = cls + + PrimitiveType.__init__(self, format) + + def pack(self, val): + return PrimitiveType.pack(self, *val) + + def pack_into(self, buf, offset, val): + return PrimitiveType.pack_into(self, buf, offset, *val) + + def unpack(self, data): + vals = PrimitiveType.unpack(self, data) + rval = self.cls(*vals) + + return (rval,) + + def unpack_from(self, buf, offset): + vals = PrimitiveType.unpack_from(self, buf, offset) + rval = self.cls(*vals) + + return (rval,) + + +class DynamicType(object): + def __new__(cls, *args, **kwargs): + return cls.pack(*args, **kwargs) + + @classmethod + def size(cls, val): + raise NotImplementedError + + @classmethod + def pack(cls, val): + raise NotImplementedError + + @classmethod + def pack_into(cls, buf, offset, val): + raise NotImplementedError + + @classmethod + def read(cls, fd): + raise NotImplementedError + + @classmethod + def unpack_from(cls, buf, offset): + raise NotImplementedError + + @classmethod + def unpack(cls, buf): + return cls.unpack_from(buf, 0) + + +class TwosComplement(PrimitiveType): + def __init__(self, primitive): + self.primitive = primitive + + bits = self.primitive.size * 8 + + self.maxval = 1 << bits + self.midval = self.maxval >> 1 + + self.upper = self.midval - 1 + self.lower = -self.midval + + @property + def size(self): + return 3 + + def pack(self, val): + if val < self.lower or val > self.upper: + msg = "{0} format requires {1} <= number <= {2}".format(self.primitive.format, + self.lower, self.upper) + raise struct_error(msg) + + if val < 0: + val = val + self.maxval + + return self.primitive.pack(val) + + def pack_into(self, buf, offset, val): + if val < self.lower or val > self.upper: + msg = "{0} format requires {1} <= number <= {2}".format(self.primitive.format, + self.lower, self.upper) + raise struct_error(msg) + + if val < 0: + val = val + self.maxval + + return self.primitive.pack_into(buf, offset, val) + + def unpack(self, data): + val = self.primitive.unpack(data)[0] + + if val & self.midval: + val = val - self.maxval + + return (val,) + + def unpack_from(self, buf, offset): + val = self.primitive.unpack_from(buf, offset)[0] + + if val & self.midval: + val = val - self.maxval + + return (val,) + + +class HighLowCombo(PrimitiveType): + def __init__(self, format, highbits, reverse=True): + PrimitiveType.__init__(self, format) + + self.highbits = highbits + self.lowmask = (1 << highbits) - 1 + self.reverse = reverse + self.lower = 0 + self.upper = (1 << (self.size * 8)) - 1 + + def pack(self, val): + if val < self.lower or val > self.upper: + msg = "{0} format requires {1} <= number <= {2}".format(self.format, + self.lower, self.upper) + raise struct_error(msg) + + if self.reverse: + high = val >> self.highbits + low = val & self.lowmask + else: + high = val & self.lowmask + low = val >> self.highbits + + return PrimitiveType.pack(self, high, low) + + def pack_into(self, buf, offset, val): + if val < self.lower or val > self.upper: + msg = "{0} format requires {1} <= number <= {2}".format(self.format, + self.lower, self.upper) + raise struct_error(msg) + + if self.reverse: + high = val >> self.highbits + low = val & self.lowmask + else: + high = val & self.lowmask + low = val >> self.highbits + + return PrimitiveType.pack_into(self, buf, offset, high, low) + + def unpack(self, data): + high, low = PrimitiveType.unpack(self, data) + + if self.reverse: + ret = high << self.highbits + ret |= low + else: + ret = high + ret |= low << self.highbits + + return (ret,) + + def unpack_from(self, buf, offset): + high, low = PrimitiveType.unpack_from(self, buf, offset) + + if self.reverse: + ret = high << self.highbits + ret |= low + else: + ret = high + ret |= low << self.highbits + + return (ret,) + + + +class FixedPoint(PrimitiveType): + def __init__(self, format, bits): + self.divider = float(1 << bits) + + PrimitiveType.__init__(self, format) + + def pack(self, val): + val *= self.divider + + return PrimitiveType.pack(self, int(val)) + + def pack_into(self, buf, offset, val): + val *= self.divider + + return PrimitiveType.pack_into(self, buf, offset, int(val)) + + def unpack(self, data): + val = PrimitiveType.unpack(self, data)[0] + val /= self.divider + + return (val,) + + def unpack(self, buf, offset): + val = PrimitiveType.unpack_from(self, buf, offset)[0] + val /= self.divider + + return (val,) + +class PaddedBytes(PrimitiveType): + def __init__(self, size, padding): + self.padded_size = size + self.padding = bytes(padding, "ascii") + + @property + def size(self): + return self.padded_size + + def pack(self, val): + rval = bytes(val[:self.size], "ascii") + + if len(rval) < self.size: + paddinglen = self.size - len(rval) + rval += self.padding * paddinglen + + return rval + + def pack_into(self, buf, offset, val): + offset = pack_bytes_into(buf, offset, + bytes(val[:self.size], "ascii")) + + if len(rval) < self.size: + paddinglen = self.size - len(rval) + offset = pack_bytes_into(buf, offset, self.padding * paddinglen) + + def unpack(self, data): + return (str(data.rstrip(self.padding), "ascii"),) + + def unpack_from(self, buf, offset): + data = buf[offset:offset + self.padded_size] + return (str(data.rstrip(self.padding), "ascii"),) + +""" 8-bit integer """ + +U8 = PrimitiveType("B") +S8 = PrimitiveType("b") + + +""" 16-bit integer """ + +U16BE = PrimitiveType(">H") +S16BE = PrimitiveType(">h") +U16LE = PrimitiveType("HB", 8, True) +S24BE = TwosComplement(U24BE) +U24LE = HighLowCombo("I") +S32BE = PrimitiveType(">i") +U32LE = PrimitiveType("Q") +U64LE = PrimitiveType("H", 8) +S8_8BE = FixedPoint(">h", 8) +U16_16BE = FixedPoint("d") + + +""" Various types """ + +FourCC = PaddedBytes(4, " ") + + +""" Script data types """ + +ScriptDataNumber = DoubleBE +ScriptDataBoolean = PrimitiveType("?") + +class U3264(DynamicType): + @classmethod + def size(cls, val, version): + if version == 1: + return U64BE.size + else: + return U32BE.size + + @classmethod + def pack(cls, val, version): + if version == 1: + return U64BE(val) + else: + return U32BE(val) + + @classmethod + def pack_into(cls, buf, offset, val, version): + if version == 1: + prim = U64BE + else: + prim = U32BE + + prim.pack_into(buf, offset, val) + + return offset + prim.size + + @classmethod + def read(cls, fd, version): + if version == 1: + return U64BE.read(fd) + else: + return U32BE.read(fd) + + @classmethod + def unpack_from(cls, buf, offset): + if version == 1: + prim = U64BE + else: + prim = U32BE + + rval = prim.unpack_from(buf, offset) + offset += prim.size + + return (rval, offset) + + +class String(DynamicType): + @classmethod + def size(cls, *args, **kwargs): + return len(cls.pack(*args, **kwargs)) + + @classmethod + def pack(cls, val, encoding="utf8", errors="ignore"): + rval = val.encode(encoding, errors) + + return rval + + @classmethod + def pack_into(cls, buf, offset, val, + encoding="utf8", errors="ignore"): + + return pack_bytes_into(buf, offset, + val.encode(encoding, errors)) + +class CString(String): + EndMarker = b"\x00" + + @classmethod + def pack(cls, *args, **kwargs): + rval = String.pack(*args, **kwargs) + rval += CString.EndMarker + + return rval + + @classmethod + def pack_into(cls, buf, offset, *args, **kwargs): + offset = String.pack_into(buf, offset, *args, **kwargs) + U8.pack_into(buf, offset, 0) + + return offset + 1 + + @classmethod + def read(cls, fd, encoding="utf8", errors="ignore"): + rval = b"" + + while True: + ch = fd.read(1) + + if len(ch) == 0 or ch == CString.EndMarker: + break + + rval += ch + + return rval.decode(encoding, errors) + + @classmethod + def unpack_from(cls, buf, offset, encoding="utf8", errors="ignore"): + end = buf[offset:].find(b"\x00") + rval = buf[offset:offset + end].decode(encoding, errors) + offset += end + 1 + + return (rval, offset) + + +class ScriptDataType(object): + __identifier__ = 0 + +class ScriptDataString(String): + __size_primitive__ = U16BE + + @classmethod + def pack(cls, val, *args, **kwargs): + rval = String.pack(val, *args, **kwargs) + size = cls.__size_primitive__(len(rval)) + + return size + rval + + @classmethod + def pack_into(cls, buf, offset, val, *args, **kwargs): + noffset = String.pack_into(buf, offset + cls.__size_primitive__.size, + val, *args, **kwargs) + + cls.__size_primitive__.pack_into(buf, offset, + (noffset - offset) - cls.__size_primitive__.size) + + return noffset + + @classmethod + def read(cls, fd, encoding="utf8", errors="ignore"): + size = cls.__size_primitive__.read(fd) + data = fd.read(size) + + return data.decode(encoding, errors) + + @classmethod + def unpack_from(cls, buf, offset, encoding="utf8", errors="ignore"): + size = cls.__size_primitive__.unpack_from(buf, offset)[0] + offset += cls.__size_primitive__.size + + data = buf[offset:offset + size].decode(encoding, errors) + offset += size + + return (data, offset) + +class ScriptDataLongString(ScriptDataString): + __size_primitive__ = U32BE + + +class ScriptDataObjectEnd(Exception): + pass + +class ScriptDataObject(OrderedDict, ScriptDataType): + __identifier__ = SCRIPT_DATA_TYPE_OBJECT + + @classmethod + def size(cls, val): + size = 3 + + for key, value in val.items(): + size += ScriptDataString.size(key) + size += ScriptDataValue.size(value) + + return size + + @classmethod + def pack(cls, val): + rval = b"" + + for key, value in val.items(): + rval += ScriptDataString(key) + rval += ScriptDataValue.pack(value) + + # Zero length key + object end identifier ends object + rval += ScriptDataString("") + rval += U8(SCRIPT_DATA_TYPE_OBJECTEND) + + return rval + + @classmethod + def pack_into(cls, buf, offset, val): + for key, value in val.items(): + offset = ScriptDataString.pack_into(buf, offset, key) + offset = ScriptDataValue.pack_into(buf, offset, value) + + # Zero length key + object end identifier ends object + offset = ScriptDataString.pack_into(buf, offset, "") + U8.pack_into(buf, offset, SCRIPT_DATA_TYPE_OBJECTEND) + + return offset + U8.size + + @classmethod + def read(cls, fd): + rval = cls() + + while True: + try: + key = ScriptDataString.read(fd) + value = ScriptDataValue.read(fd) + except ScriptDataObjectEnd: + break + + if len(key) == 0: + break + + rval[key] = value + + return rval + + @classmethod + def unpack_from(cls, buf, offset): + rval = cls() + + while True: + try: + key, offset = ScriptDataString.unpack_from(buf, offset) + value, offset = ScriptDataValue.unpack_from(buf, offset) + except ScriptDataObjectEnd: + offset += 1 + break + + if len(key) == 0: + break + + rval[key] = value + + return (rval, offset) + + +class ScriptDataECMAArray(ScriptDataObject): + __identifier__ = SCRIPT_DATA_TYPE_ECMAARRAY + + @classmethod + def size(cls, val): + return 4 + ScriptDataObject.size(val) + + @classmethod + def pack(cls, val): + rval = U32BE(len(val)) + rval += ScriptDataObject.pack(val) + + return rval + + @classmethod + def pack_into(cls, buf, offset, val): + U32BE.pack_into(buf, offset, len(val)) + + return ScriptDataObject.pack_into(buf, offset + U32BE.size, + val) + + @classmethod + def read(cls, fd): + length = U32BE.read(fd) + val = ScriptDataObject.read(fd) + + return cls(val) + + @classmethod + def unpack_from(cls, buf, offset): + length = U32BE.unpack_from(buf, offset) + offset += U32BE.size + + val, offset = ScriptDataObject.unpack_from(buf, offset) + + return (cls(val), offset) + +class ScriptDataStrictArray(DynamicType): + @classmethod + def size(cls, val): + size = 4 + + for sdval in val: + size += ScriptDataValue.size(sdval) + + return size + + @classmethod + def pack(cls, val): + rval = U32BE(len(val)) + + for sdval in val: + rval += ScriptDataValue.pack(sdval) + + return rval + + @classmethod + def pack_into(cls, buf, offset, val): + U32BE.pack_into(buf, offset, len(val)) + offset += U32BE.size + + for sdval in val: + offset = ScriptDataValue.pack_into(buf, offset, sdval) + + return offset + + @classmethod + def read(cls, fd): + length = U32BE.read(fd) + rval = [] + + for i in range(length): + val = ScriptDataValue.read(fd) + rval.append(val) + + return rval + + @classmethod + def unpack_from(cls, buf, offset): + length = U32BE.unpack_from(buf, offset)[0] + offset += U32BE.size + rval = [] + + for i in range(length): + val, offset = ScriptDataValue.unpack_from(buf, offset) + rval.append(val) + + return (rval, offset) + + +ScriptDataDate = namedtuple("ScriptDataDate", ["timestamp", "offset"]) +ScriptDataDateStruct = PrimitiveClassType(">dh", ScriptDataDate) +ScriptDataDate.__identifier__ = SCRIPT_DATA_TYPE_DATE +ScriptDataDate.__packer__ = ScriptDataDateStruct + +ScriptDataReference = namedtuple("ScriptDataReference", ["reference"]) +ScriptDataReferenceStruct = PrimitiveClassType(">H", ScriptDataReference) +ScriptDataReference.__identifier__ = SCRIPT_DATA_TYPE_REFERENCE +ScriptDataReference.__packer__ = ScriptDataReferenceStruct + + +class ScriptDataValue(DynamicType, ScriptDataType): + # key: identifier, value: unpacker class + PrimitiveReaders = { + SCRIPT_DATA_TYPE_NUMBER: ScriptDataNumber, + SCRIPT_DATA_TYPE_BOOLEAN: ScriptDataBoolean, + SCRIPT_DATA_TYPE_REFERENCE: ScriptDataReferenceStruct, + SCRIPT_DATA_TYPE_DATE: ScriptDataDateStruct, + } + + DynamicReaders = { + SCRIPT_DATA_TYPE_STRING: ScriptDataString, + SCRIPT_DATA_TYPE_LONGSTRING: ScriptDataLongString, + SCRIPT_DATA_TYPE_OBJECT: ScriptDataObject, + SCRIPT_DATA_TYPE_ECMAARRAY: ScriptDataECMAArray, + SCRIPT_DATA_TYPE_STRICTARRAY: ScriptDataStrictArray, + } + + Readers = PrimitiveReaders.copy() + Readers.update(DynamicReaders) + + @classmethod + def size(cls, val): + size = 1 + + if isinstance(val, bool): + size += ScriptDataBoolean.size + + elif isinstance(val, (int, float)): + size += ScriptDataNumber.size + + elif isinstance(val, list): + size += ScriptDataStrictArray.size(val) + + elif isstring(val): + if len(val) > 0xFFFF: + size += ScriptDataLongString.size(val) + else: + size += ScriptDataString.size(val) + + elif isinstance(val, ScriptDataType): + cls = type(val) + size += cls.size(val) + + elif type(val) in (ScriptDataDate, ScriptDataReference): + cls = type(val) + packer = cls.__packer__ + size += packer.size + + return size + + @classmethod + def pack(cls, val): + rval = b"" + + if isinstance(val, bool): + rval += U8(SCRIPT_DATA_TYPE_BOOLEAN) + rval += ScriptDataBoolean(val) + + elif isinstance(val, (int, float)): + rval += U8(SCRIPT_DATA_TYPE_NUMBER) + rval += ScriptDataNumber(val) + + elif isinstance(val, list): + rval += U8(SCRIPT_DATA_TYPE_STRICTARRAY) + rval += ScriptDataStrictArray(val) + + elif isstring(val): + if len(val) > 0xFFFF: + rval += U8(SCRIPT_DATA_TYPE_LONGSTRING) + rval += ScriptDataLongString(val) + else: + rval += U8(SCRIPT_DATA_TYPE_STRING) + rval += ScriptDataString(val) + + elif val is None: + rval += U8(SCRIPT_DATA_TYPE_NULL) + + elif isinstance(val, ScriptDataType): + cls = type(val) + rval += U8(cls.__identifier__) + rval += cls.pack(val) + + elif type(val) in (ScriptDataDate, ScriptDataReference): + cls = type(val) + packer = cls.__packer__ + + rval += U8(cls.__identifier__) + rval += packer.pack(val) + + else: + raise ValueError("Unable to pack value of type {0}".format(type(val))) + + return rval + + @classmethod + def pack_into(cls, buf, offset, val): + if isinstance(val, bool): + U8.pack_into(buf, offset, SCRIPT_DATA_TYPE_BOOLEAN) + offset += U8.size + + ScriptDataBoolean.pack_into(buf, offset, val) + offset += ScriptDataBoolean.size + + elif isinstance(val, (int, float)): + U8.pack_into(buf, offset, SCRIPT_DATA_TYPE_NUMBER) + offset += U8.size + + ScriptDataNumber.pack_into(buf, offset, val) + offset += ScriptDataNumber.size + + elif isinstance(val, list): + U8.pack_into(buf, offset, SCRIPT_DATA_TYPE_STRICTARRAY) + offset += U8.size + offset = ScriptDataStrictArray.pack_into(buf, offset, val) + + elif isstring(val): + if len(val) > 0xFFFF: + U8.pack_into(buf, offset, SCRIPT_DATA_TYPE_LONGSTRING) + offset += U8.size + offset = ScriptDataLongString.pack_into(buf, offset, val) + else: + U8.pack_into(buf, offset, SCRIPT_DATA_TYPE_STRING) + offset += U8.size + offset = ScriptDataString.pack_into(buf, offset, val) + + elif val is None: + U8.pack_into(buf, offset, SCRIPT_DATA_TYPE_NULL) + + elif isinstance(val, ScriptDataType): + cls = type(val) + U8.pack_into(buf, offset, cls.__identifier__) + offset += U8.size + offset = cls.pack_into(buf, offset, val) + + elif type(val) in (ScriptDataDate, ScriptDataReference): + cls = type(val) + packer = cls.__packer__ + + U8.pack_into(buf, offset, cls.__identifier__) + offset += U8.size + + packer.pack_into(buf, offset, val) + offset += packer.size + + else: + raise ValueError("Unable to pack value of type {0}".format(type(val))) + + return offset + + @classmethod + def read(cls, fd): + type_ = U8.read(fd) + + if type_ in ScriptDataValue.Readers: + return ScriptDataValue.Readers[type_].read(fd) + + elif type_ == SCRIPT_DATA_TYPE_OBJECTEND: + raise ScriptDataObjectEnd + + elif (type_ == SCRIPT_DATA_TYPE_NULL or + type_ == SCRIPT_DATA_TYPE_UNDEFINED): + + return None + + else: + raise IOError("Unhandled script data type: {0}".format(type_)) + + @classmethod + def unpack_from(cls, buf, offset): + type_ = U8.unpack_from(buf, offset)[0] + offset += U8.size + + if type_ in ScriptDataValue.DynamicReaders: + return ScriptDataValue.Readers[type_].unpack_from(buf, offset) + + elif type_ in ScriptDataValue.PrimitiveReaders: + reader = ScriptDataValue.PrimitiveReaders[type_] + rval = reader.unpack_from(buf, offset)[0] + offset += reader.size + + return (rval, offset) + + elif type_ == SCRIPT_DATA_TYPE_OBJECTEND: + raise ScriptDataObjectEnd + + elif (type_ == SCRIPT_DATA_TYPE_NULL or + type_ == SCRIPT_DATA_TYPE_UNDEFINED): + + return (None, offset) + + else: + raise IOError("Unhandled script data type: {0}".format(type_)) + + + diff --git a/src/livestreamer/packages/flashmedia/util.py b/src/livestreamer/packages/flashmedia/util.py index 237cf741..a99a50be 100644 --- a/src/livestreamer/packages/flashmedia/util.py +++ b/src/livestreamer/packages/flashmedia/util.py @@ -2,9 +2,11 @@ from .compat import bytes, is_py2 +import struct + def isstring(val): if is_py2: - return isinstance(val, str) or isinstance(val, unicode) + return isinstance(val, (str, unicode)) else: return isinstance(val, str) @@ -53,5 +55,50 @@ def iso639_to_lang(iso639): return res -__all__ = ["byte", "isstring", "flagproperty", "lang_to_iso639", "iso639_to_lang"] + +def pack_many_into(buf, offset, types, values): + for packer, value in zip(types, values): + packer.pack_into(buf, offset, value) + offset += packer.size + + return offset + +def pack_bytes_into(buf, offset, data): + size = len(data) + fmt = str(size) + "s" + struct.pack_into(fmt, buf, offset, data) + + return offset + size + +def unpack_many_from(buf, offset, types): + rval = tuple() + + for unpacker in types: + rval += unpacker.unpack_from(buf, offset) + offset += unpacker.size + + return rval + +def chunked_read(fd, length, chunk_size=8192, exception=IOError): + chunks = [] + data_left = length + + while data_left > 0: + try: + data = fd.read(min(8192, data_left)) + except IOError as err: + raise exception("Failed to read data: {0}".format(str(err))) + + if not data: + raise exception("End of stream before requied data could be read") + + data_left -= len(data) + chunks.append(data) + + return b"".join(chunks) + + +__all__ = ["byte", "isstring", "flagproperty", "lang_to_iso639", + "iso639_to_lang", "pack_many_into", "pack_bytes_into", + "unpack_many_from", "chunked_read"]