python-librtmp-0.3.0/0000755000175000017500000000000012530710447015603 5ustar chrippachrippa00000000000000python-librtmp-0.3.0/librtmp/0000755000175000017500000000000012530710447017254 5ustar chrippachrippa00000000000000python-librtmp-0.3.0/librtmp/packet.py0000644000175000017500000000666012527415446021114 0ustar chrippachrippa00000000000000from . import ffi, librtmp __all__ = ["RTMPPacket", "PACKET_SIZE_LARGE", "PACKET_SIZE_MEDIUM", "PACKET_SIZE_SMALL", "PACKET_SIZE_MINIMUM", "PACKET_TYPE_CHUNK_SIZE", "PACKET_TYPE_BYTES_READ_REPORT", "PACKET_TYPE_CONTROL", "PACKET_TYPE_SERVER_BW", "PACKET_TYPE_CLIENT_BW", "PACKET_TYPE_AUDIO", "PACKET_TYPE_VIDEO", "PACKET_TYPE_FLEX_STREAM_SEND", "PACKET_TYPE_FLEX_SHARED_OBJECT", "PACKET_TYPE_FLEX_MESSAGE", "PACKET_TYPE_INFO", "PACKET_TYPE_SHARED_OBJECT", "PACKET_TYPE_INVOKE", "PACKET_TYPE_FLASH_VIDEO"] (PACKET_SIZE_LARGE, PACKET_SIZE_MEDIUM, PACKET_SIZE_SMALL, PACKET_SIZE_MINIMUM) = range(4) PACKET_TYPE_CHUNK_SIZE = 0x01 PACKET_TYPE_BYTES_READ_REPORT = 0x03 PACKET_TYPE_CONTROL = 0x04 PACKET_TYPE_SERVER_BW = 0x05 PACKET_TYPE_CLIENT_BW = 0x06 PACKET_TYPE_AUDIO = 0x08 PACKET_TYPE_VIDEO = 0x09 PACKET_TYPE_FLEX_STREAM_SEND = 0x0F PACKET_TYPE_FLEX_SHARED_OBJECT = 0x10 PACKET_TYPE_FLEX_MESSAGE = 0x11 PACKET_TYPE_INFO = 0x12 PACKET_TYPE_SHARED_OBJECT = 0x13 PACKET_TYPE_INVOKE = 0x14 PACKET_TYPE_FLASH_VIDEO = 0x16 class RTMPPacket(object): @classmethod def _from_pointer(cls, pointer): packet = cls.__new__(cls) packet.packet = pointer return packet def __init__(self, type, format, channel, timestamp=0, absolute_timestamp=False, body=None): self.packet = ffi.new("RTMPPacket*") self.type = type self.format = format self.channel = channel self.timestamp = timestamp self.absolute_timestamp = absolute_timestamp if not body: body = b"" self.body = body @property def format(self): """Format of the packet.""" return self.packet.m_headerType @format.setter def format(self, value): self.packet.m_headerType = int(value) @property def type(self): """Type of the packet.""" return self.packet.m_packetType @type.setter def type(self, value): self.packet.m_packetType = int(value) @property def channel(self): """Channel of the packet.""" return self.packet.m_nChannel @channel.setter def channel(self, value): self.packet.m_nChannel = int(value) @property def timestamp(self): """Timestamp of the packet.""" return self.packet.m_nTimeStamp @timestamp.setter def timestamp(self, value): self.packet.m_nTimeStamp = int(value) @property def absolute_timestamp(self): """True if the timestamp is absolute.""" return bool(self.packet.m_hasAbsTimestamp) @absolute_timestamp.setter def absolute_timestamp(self, value): self.packet.m_hasAbsTimestamp = int(bool(value)) @property def body(self): """The body of the packet.""" view = ffi.buffer(self.packet.m_body, self.packet.m_nBodySize) return view[:] @body.setter def body(self, value): size = len(value) librtmp.RTMPPacket_Alloc(self.packet, size) view = ffi.buffer(self.packet.m_body, size) view[:] = value self.packet.m_nBodySize = size def dump(self): """Dumps packet to logger.""" librtmp.RTMPPacket_Dump(self.packet) def __del__(self): librtmp.RTMPPacket_Free(self.packet) python-librtmp-0.3.0/librtmp/__init__.py0000644000175000017500000000126112530704672021370 0ustar chrippachrippa00000000000000"""Python bindings for librtmp, built with cffi.""" from .compat import is_win32 if is_win32: import socket # Import socket to initialize WinSock from ._librtmp import ffi, lib as librtmp librtmp_version = librtmp.RTMP_LibVersion() if librtmp_version < 0x020300: raise ImportError("Only librtmp version >= 2.3 is supported by this library") from .exceptions import RTMPError, RTMPTimeoutError from .logging import * from .packet import * from .rtmp import * from .stream import * from .utils import * __title__ = "python-librtmp" __version__ = "0.3.0" __license__ = "Simplified BSD" __author__ = "Christopher Rosell" __copyright__ = "Copyright 2013-2015 Christopher Rosell" python-librtmp-0.3.0/librtmp/amf.py0000644000175000017500000001102112527415446020373 0ustar chrippachrippa00000000000000try: from functools import singledispatch except ImportError: from singledispatch import singledispatch from . import ffi, librtmp from .aval import AVal from .compat import is_py2, bytes, range from .exceptions import AMFError AMF_STRING_TYPES = (librtmp.AMF_STRING, librtmp.AMF_LONG_STRING) AMF_OBJECT_DICT_TYPES = (librtmp.AMF_OBJECT, librtmp.AMF_ECMA_ARRAY) __all__ = ["AMFObject", "decode_amf", "encode_amf"] class AMFObject(dict): pass def _create_buffer(size): pbuf = ffi.new("char[]", size) pend = pbuf + size buf = ffi.buffer(pbuf, size) return pbuf, pend, buf def _encode_key_name(key): key = bytes(key, "utf8") key_len = len(key) pbuf, pend, buf = _create_buffer(key_len + 2) librtmp.AMF_EncodeInt16(pbuf, pend, key_len) buf[2:key_len + 2] = key return buf[:] @singledispatch def encoder(val): raise AMFError("Unable to encode '{0}' type".format(type(val).__name__)) @encoder.register(type(None)) def _encode_none(val): return bytes((librtmp.AMF_NULL,)) @encoder.register(str) def _encode_str(val): val = AVal(val) pbuf, pend, buf = _create_buffer(val.aval.av_len + 1 + 4) res = librtmp.AMF_EncodeString(pbuf, pend, val.aval) size = res - pbuf return buf[:size] if is_py2: encoder.register(unicode, _encode_str) @encoder.register(float) @encoder.register(int) def _encode_number(val): val = float(val) pbuf, pend, buf = _create_buffer(9) librtmp.AMF_EncodeNumber(pbuf, pend, val) return buf[:] if is_py2: encoder.register(long, _encode_number) @encoder.register(bool) def _encode_boolean(val): pbuf, pend, buf = _create_buffer(2) librtmp.AMF_EncodeBoolean(pbuf, pend, int(val)) return buf[:] @encoder.register(AMFObject) def _encode_object(val): phead, headend, head = _create_buffer(4) head[0] = bytes((librtmp.AMF_OBJECT,)) librtmp.AMF_EncodeInt24(phead + 1, headend, librtmp.AMF_OBJECT_END) body = bytearray() for key, value in val.items(): body += _encode_key_name(key) body += encoder(value) return head[:1] + bytes(body) + head[1:] @encoder.register(dict) def _encode_ecma_array(val): phead, headend, head = _create_buffer(8) head[0] = bytes((librtmp.AMF_ECMA_ARRAY,)) librtmp.AMF_EncodeInt32(phead + 1, headend, len(val)) librtmp.AMF_EncodeInt24(phead + 5, headend, librtmp.AMF_OBJECT_END) body = bytearray() for key, value in val.items(): body += _encode_key_name(key) body += encoder(value) return head[:5] + bytes(body) + head[5:] @encoder.register(list) def _encode_array(val): phead, headend, head = _create_buffer(5) head[0] = bytes((librtmp.AMF_STRICT_ARRAY,)) librtmp.AMF_EncodeInt32(phead + 1, headend, len(val)) body = bytearray() for value in val: body += encoder(value) return head[:] + bytes(body) def _decode_prop(prop): prop_type = librtmp.AMFProp_GetType(prop) if prop_type == librtmp.AMF_NUMBER: val = librtmp.AMFProp_GetNumber(prop) elif prop_type in AMF_STRING_TYPES: aval = AVal() librtmp.AMFProp_GetString(prop, aval.aval) val = aval.value.decode("utf8", "ignore") elif prop_type == librtmp.AMF_BOOLEAN: val = bool(librtmp.AMFProp_GetBoolean(prop)) elif prop_type in AMF_OBJECT_DICT_TYPES: if prop_type == librtmp.AMF_OBJECT: val = AMFObject() else: val = dict() for key, value in _decode_prop_obj(prop): val[key] = value elif prop_type == librtmp.AMF_STRICT_ARRAY: val = [] for key, value in _decode_prop_obj(prop): val.append(value) else: val = None return val def _decode_prop_obj(prop): obj = ffi.new("AMFObject*") librtmp.AMFProp_GetObject(prop, obj) prop_count = librtmp.AMF_CountProp(obj) for i in range(prop_count): prop = librtmp.AMF_GetProp(obj, ffi.NULL, i) key = AVal() librtmp.AMFProp_GetName(prop, key.aval) key = key.value.decode("utf8", "ignore") value = _decode_prop(prop) yield key, value def encode_amf(value): return encoder(value) def decode_amf(body): obj = ffi.new("AMFObject*") res = librtmp.AMF_Decode(obj, body, len(body), 0) if res == ffi.NULL: raise AMFError("Unable to decode AMF data") rval = [] prop_count = librtmp.AMF_CountProp(obj) for i in range(prop_count): prop = librtmp.AMF_GetProp(obj, ffi.NULL, i) val = _decode_prop(prop) rval.append(val) return rval python-librtmp-0.3.0/librtmp/compat.py0000644000175000017500000000137512526707002021115 0ustar chrippachrippa00000000000000import os import sys is_py2 = (sys.version_info[0] == 2) is_py3 = (sys.version_info[0] == 3) is_win32 = os.name == "nt" if is_py2: _str = str str = unicode range = xrange byte_types = _str integer_types = (int, long) string_types = (_str, unicode) def bytes(b=None, enc="ascii"): if b is None: return "" elif isinstance(b, list) or isinstance(b, tuple): return "".join([chr(i) for i in b]) else: return _str(b) elif is_py3: bytes = bytes str = str range = range byte_types = bytes integer_types = int string_types = str __all__ = ["is_py2", "is_py3", "is_win32", "str", "bytes", "range", "byte_types", "integer_types", "string_types"] python-librtmp-0.3.0/librtmp/stream.py0000644000175000017500000000603612527415446021135 0ustar chrippachrippa00000000000000from io import IOBase from . import ffi, librtmp from .compat import byte_types from .exceptions import RTMPError __all__ = ["RTMPStream"] class RTMPStream(IOBase): """A file-like interface to a stream within a RTMP session.""" def __init__(self, client, update_buffer=True): self.client = client self._buf = self._view = None self._closed = False self._update_buffer = update_buffer self._updated_buffer = False def read(self, size): """Attempts to read data from the stream. :param size: int, The maximum amount of bytes to read. Raises :exc:`IOError` on error. """ # If enabled tell the server that our buffer can fit the whole # stream, this often increases throughput alot. if self._update_buffer and not self._updated_buffer and self.duration: self.update_buffer((self.duration * 1000) + 5000) self._updated_buffer = True if not self._buf or len(self._buf) != size: self._buf = ffi.new("char[]", size) self._view = ffi.buffer(self._buf, size) res = librtmp.RTMP_Read(self.client.rtmp, self._buf, size) if res < 0: raise IOError("Failed to read data") return self._view[:res] def write(self, data): """Writes data to the stream. :param data: bytes, FLV data to write to the stream The data passed can contain multiple FLV tags, but it MUST always contain complete tags or undefined behaviour might occur. Raises :exc:`IOError` on error. """ if isinstance(data, bytearray): data = bytes(data) if not isinstance(data, byte_types): raise ValueError("A bytes argument is required") res = librtmp.RTMP_Write(self.client.rtmp, data, len(data)) if res < 0: raise IOError("Failed to write data") return res def pause(self): """Pauses the stream.""" res = librtmp.RTMP_Pause(self.client.rtmp, 1) if res < 1: raise RTMPError("Failed to pause") def unpause(self): """Unpauses the stream.""" res = librtmp.RTMP_Pause(self.client.rtmp, 0) if res < 1: raise RTMPError("Failed to unpause") def seek(self, time): """Attempts to seek in the stream. :param time: int, Time to seek to in seconds """ res = librtmp.RTMP_SendSeek(self.client.rtmp, time) if res < 1: raise RTMPError("Failed to seek") def close(self): """Closes the connection.""" if not self._closed: self._closed = True self.client.close() def update_buffer(self, ms): """Tells the server how big our buffer is (in milliseconds).""" librtmp.RTMP_SetBufferMS(self.client.rtmp, int(ms)) librtmp.RTMP_UpdateBufferMS(self.client.rtmp) @property def duration(self): """The duration of the stream.""" return librtmp.RTMP_GetDuration(self.client.rtmp) python-librtmp-0.3.0/librtmp/logging.py0000644000175000017500000000301112527415446021256 0ustar chrippachrippa00000000000000from . import ffi, librtmp from .utils import add_signal_handler __all__ = ["set_log_level", "get_log_level", "set_log_output", "add_log_callback", "remove_log_callback", "LOG_CRIT", "LOG_ERROR", "LOG_WARNING", "LOG_INFO", "LOG_DEBUG", "LOG_DEBUG2", "LOG_ALL"] (LOG_CRIT, LOG_ERROR, LOG_WARNING, LOG_INFO, LOG_DEBUG, LOG_DEBUG2, LOG_ALL) = range(1, 8) _log_callbacks = set() _log_level = LOG_ALL _log_output = None def set_log_level(level): """Sets log level.""" global _log_level _log_level = level def get_log_level(): """Returns current log level.""" return _log_level def set_log_output(fd): """Sets log output to a open file-object.""" global _log_output _log_output = fd def add_log_callback(callback): """Adds a log callback.""" global _log_callbacks if not callable(callback): raise ValueError("Callback must be callable") _log_callbacks.add(callback) return callback def remove_log_callback(callback): """Removes a log callback.""" global _log_callbacks _log_callbacks.remove(callback) @ffi.callback("void(int,char*)") def _log_callback(level, msg): msg = ffi.string(msg) msg = msg.decode("utf8", "ignore") for callback in _log_callbacks: callback(level, msg) if hasattr(_log_output, "write") and level <= _log_level: _log_output.write(msg + "\n") librtmp.python_log_callback = _log_callback librtmp.RTMP_LogSetCallback(librtmp.c_log_callback) add_signal_handler() python-librtmp-0.3.0/librtmp/aval.py0000644000175000017500000000142312527415446020560 0ustar chrippachrippa00000000000000from . import ffi from .compat import bytes, integer_types, string_types __all__ = ["AVal"] class AVal(object): def __init__(self, value=None): self.aval = ffi.new("AVal *") if value is not None: self.value = value @property def value(self): buf = ffi.buffer(self.aval.av_val, self.aval.av_len) return buf[:] @value.setter def value(self, value): if isinstance(value, integer_types): value = str(value) if isinstance(value, string_types): value = bytes(value, "utf8") elif isinstance(value, bool): value = str(value).lower() self.value_str = ffi.new("char[]", value) self.aval.av_val = self.value_str self.aval.av_len = len(value) python-librtmp-0.3.0/librtmp/rtmp.py0000644000175000017500000003773012527415446020631 0ustar chrippachrippa00000000000000from binascii import unhexlify from time import time from . import ffi, librtmp from .aval import AVal from .amf import encode_amf, decode_amf, AMFError from .compat import bytes, string_types, integer_types from .exceptions import RTMPError, RTMPTimeoutError from .packet import RTMPPacket, PACKET_TYPE_INVOKE, PACKET_SIZE_MEDIUM from .stream import RTMPStream from .utils import hash_swf __all__ = ["RTMP", "RTMPCall"] class RTMP(object): """ A RTMP client session. :param url: str, A RTMP URL in the format `rtmp[t][e|s]://hostname[:port][/app[/playpath]]`. :param playpath: str, Overrides the playpath parsed from the RTMP URL. :param tcurl: str, URL of the target stream. Defaults to `rtmp[t][e|s]://host[:port]/app`. :param app: str, Name of application to connect to on the RTMP server. :param pageurl: str, URL of the web page in which the media was embedded. :param auth: str, Authentication string to be appended to the connect string. :param connect_data: This value will be encoded to AMF and added to the connect packet. :param swfhash: str, SHA256 hash of the decompressed SWF file (hexdigest). :param swfsize: int, Size of the decompressed SWF file. :param swfurl: str, URL of the SWF player for the media. :param swfvfy: bool, Calculate the correct swfhash and swfsize parameter from the `swfurl` specified. :param flashver: str, Version of the Flash plugin used to run the SWF player. :param subscribe: str, Name of live stream to subscribe to. Defaults to `playpath`. :param token: str, Key for SecureToken response, used if the server requires SecureToken authentication. :param live: bool, Specify that the media is a live stream. :param jtv: str, JSON token used by Twitch/Justin.tv servers. :param socks: str, Use the specified SOCKS4 proxy. :param start: int, Start at num seconds into the stream. Not valid for live streams. :param stop: int, Stop at num seconds into the stream. :param buffer: int, Set buffer time to num milliseconds. This is used to control rate of data sent by FMS servers, not buffering of data. The default is 30000. :param timeout: int, Timeout the session after num seconds without receiving any data from the server. The default is 30. """ def __init__(self, url, playpath=None, tcurl=None, app=None, pageurl=None, auth=None, swfhash=None, swfsize=None, swfurl=None, swfvfy=None, flashver=None, subscribe=None, token=None, live=None, jtv=None, connect_data=None, socks=None, start=None, stop=None, buffer=None, timeout=None): def set_opt(key, val): if val is not None: self.set_option(key, val) self.rtmp = librtmp.RTMP_Alloc() if self.rtmp == ffi.NULL: raise MemoryError("Failed to allocate RTMP handle") librtmp.RTMP_Init(self.rtmp) self._options = dict() self._invoke_args = dict() self._invoke_handlers = dict() self._invoke_results = dict() self._connect_result = None self.url = None if swfurl and swfvfy: swfhash, swfsize = hash_swf(swfurl) if swfhash: digest = unhexlify(swfhash) librtmp.RTMP_SetSWFHash(self.rtmp, digest, swfsize) # Socks option must be set before setup_url. set_opt("socks", socks) self.setup_url(url) set_opt("playpath", playpath) set_opt("tcUrl", tcurl) set_opt("app", app) set_opt("swfUrl", swfurl) set_opt("pageUrl", pageurl) set_opt("auth", auth) set_opt("flashver", flashver) set_opt("subscribe", subscribe) set_opt("token", token) set_opt("jtv", jtv) set_opt("live", live) set_opt("start", start) set_opt("stop", stop) set_opt("buffer", buffer) set_opt("timeout", timeout) if isinstance(connect_data, (list, tuple)): for data in connect_data: self._parse_connect_data(data) elif connect_data is not None: self._parse_connect_data(connect_data) def _parse_connect_data(self, val): if isinstance(val, bool): self.set_option("conn", "B:{0}".format(int(val))) elif isinstance(val, string_types): self.set_option("conn", "S:{0}".format(val)) elif isinstance(val, integer_types): self.set_option("conn", "N:{0}".format(val)) elif isinstance(val, type(None)): self.set_option("conn", "Z:") elif isinstance(val, dict): self.set_option("conn", "O:1") for key, value in val.items(): if isinstance(value, bool): self.set_option("conn", "NB:{0}:{1}".format(key, int(value))) elif isinstance(value, string_types): self.set_option("conn", "NS:{0}:{1}".format(key, value)) elif isinstance(value, integer_types): self.set_option("conn", "NN:{0}:{1}".format(key, value)) self.set_option("conn", "O:0") def set_option(self, key, value): """Sets a option for this session. For a detailed list of available options see the librtmp(3) man page. :param key: str, A valid option key. :param value: A value, anything that can be converted to str is valid. Raises :exc:`ValueError` if a invalid option is specified. """ akey = AVal(key) aval = AVal(value) res = librtmp.RTMP_SetOpt(self.rtmp, akey.aval, aval.aval) if res < 1: raise ValueError("Unable to set option {0}".format(key)) self._options[akey] = aval def setup_url(self, url): r"""Attempt to parse a RTMP URL. Additional options may be specified by appending space-separated key=value pairs to the URL. Special characters in values may need to be escaped to prevent misinterpretation by the option parser. The escape encoding uses a backslash followed by two hexadecimal digits representing the ASCII value of the character. E.g., spaces must be escaped as `\\20` and backslashes must be escaped as `\\5c`. :param url: str, A RTMP URL in the format `rtmp[t][e|s]://hostname[:port][/app[/playpath]]` Raises :exc:`RTMPError` if URL parsing fails. """ self.url = bytes(url, "utf8") res = librtmp.RTMP_SetupURL(self.rtmp, self.url) if res < 1: raise RTMPError("Unable to parse URL") def connect(self, packet=None): """Connect to the server. :param packet: RTMPPacket, this packet will be sent instead of the regular "connect" packet. Raises :exc:`RTMPError` if the connect attempt fails. """ if isinstance(packet, RTMPPacket): packet = packet.packet else: packet = ffi.NULL res = librtmp.RTMP_Connect(self.rtmp, packet) if res < 1: raise RTMPError("Failed to connect") return RTMPCall(self, 1.0) def create_stream(self, seek=None, writeable=False, update_buffer=True): """Prepares the session for streaming of audio/video and returns a :class:`RTMPStream` object. :param seek: int, Attempt to seek to this position. :param writeable: bool, Make the stream writeable instead of readable. :param update_buffer: bool, When enabled will attempt to speed up download by telling the server our buffer can fit the whole stream. Raises :exc:`RTMPError` if a stream could not be created. Usage:: >>> stream = conn.create_stream() >>> data = stream.read(1024) """ if writeable: librtmp.RTMP_EnableWrite(self.rtmp) # Calling handle_packet() on a connect result causes # librtmp to send a CreateStream call. This is not always # desired when using process_packets(), therefore we do it # here instead. if self._connect_result: self.handle_packet(self._connect_result) if not seek: seek = 0 res = librtmp.RTMP_ConnectStream(self.rtmp, seek) if res < 1: raise RTMPError("Failed to start RTMP playback") return RTMPStream(self, update_buffer=update_buffer) @property def connected(self): """Returns True if connected to the server. Usage:: >>> conn.connected True """ return bool(librtmp.RTMP_IsConnected(self.rtmp)) def read_packet(self): """Reads a RTMP packet from the server. Returns a :class:`RTMPPacket`. Raises :exc:`RTMPError` on error. Raises :exc:`RTMPTimeoutError` on timeout. Usage:: >>> packet = conn.read_packet() >>> packet.body b'packet body ...' """ packet = ffi.new("RTMPPacket*") packet_complete = False while not packet_complete: res = librtmp.RTMP_ReadPacket(self.rtmp, packet) if res < 1: if librtmp.RTMP_IsTimedout(self.rtmp): raise RTMPTimeoutError("Timed out while reading packet") else: raise RTMPError("Failed to read packet") packet_complete = packet.m_nBytesRead == packet.m_nBodySize return RTMPPacket._from_pointer(packet) def send_packet(self, packet, queue=True): """Sends a RTMP packet to the server. :param packet: RTMPPacket, the packet to send to the server. :param queue: bool, If True, queue up the packet in a internal queue rather than sending it right away. """ if not isinstance(packet, RTMPPacket): raise ValueError("A RTMPPacket argument is required") return librtmp.RTMP_SendPacket(self.rtmp, packet.packet, int(queue)) def handle_packet(self, packet): """Lets librtmp look at a packet and send a response if needed.""" if not isinstance(packet, RTMPPacket): raise ValueError("A RTMPPacket argument is required") return librtmp.RTMP_ClientPacket(self.rtmp, packet.packet) def process_packets(self, transaction_id=None, invoked_method=None, timeout=None): """Wait for packets and process them as needed. :param transaction_id: int, Wait until the result of this transaction ID is recieved. :param invoked_method: int, Wait until this method is invoked by the server. :param timeout: int, The time to wait for a result from the server. Note: This is the timeout used by this method only, the connection timeout is still used when reading packets. Raises :exc:`RTMPError` on error. Raises :exc:`RTMPTimeoutError` on timeout. Usage:: >>> @conn.invoke_handler ... def add(x, y): ... return x + y >>> @conn.process_packets() """ start = time() while self.connected and transaction_id not in self._invoke_results: if timeout and (time() - start) >= timeout: raise RTMPTimeoutError("Timeout") packet = self.read_packet() if packet.type == PACKET_TYPE_INVOKE: try: decoded = decode_amf(packet.body) except AMFError: continue try: method, transaction_id_, obj = decoded[:3] args = decoded[3:] except ValueError: continue if method == "_result": if len(args) > 0: result = args[0] else: result = None self._invoke_results[transaction_id_] = result else: handler = self._invoke_handlers.get(method) if handler: res = handler(*args) if res is not None: self.call("_result", res, transaction_id=transaction_id_) if method == invoked_method: self._invoke_args[invoked_method] = args break if transaction_id_ == 1.0: self._connect_result = packet else: self.handle_packet(packet) else: self.handle_packet(packet) if transaction_id: result = self._invoke_results.pop(transaction_id, None) return result if invoked_method: args = self._invoke_args.pop(invoked_method, None) return args def call(self, method, *args, **params): """Calls a method on the server.""" transaction_id = params.get("transaction_id") if not transaction_id: self.transaction_id += 1 transaction_id = self.transaction_id obj = params.get("obj") args = [method, transaction_id, obj] + list(args) args_encoded = map(lambda x: encode_amf(x), args) body = b"".join(args_encoded) format = params.get("format", PACKET_SIZE_MEDIUM) channel = params.get("channel", 0x03) packet = RTMPPacket(type=PACKET_TYPE_INVOKE, format=format, channel=channel, body=body) self.send_packet(packet) return RTMPCall(self, transaction_id) def remote_method(self, method, block=False, **params): """Creates a Python function that will attempt to call a remote method when used. :param method: str, Method name on the server to call :param block: bool, Wheter to wait for result or not Usage:: >>> send_usher_token = conn.remote_method("NetStream.Authenticate.UsherToken", block=True) >>> send_usher_token("some token") 'Token Accepted' """ def func(*args): call = self.call(method, *args, **params) if block: return call.result() return call func.__name__ = method return func def invoke_handler(self, func=None, name=None): if not callable(func): return lambda f: self.invoke_handler(func=f, name=func) method = name or func.__name__ self.register_invoke_handler(method, func) return func def register_invoke_handler(self, method, func): self._invoke_handlers[method] = func def close(self): """Closes the connection to the server.""" if self.connected: librtmp.RTMP_Close(self.rtmp) @property def transaction_id(self): return librtmp.RTMP_GetInvokeCount(self.rtmp) @transaction_id.setter def transaction_id(self, val): librtmp.RTMP_SetInvokeCount(self.rtmp, int(val)) def __del__(self): librtmp.RTMP_Free(self.rtmp) class RTMPCall(object): """A RTMP call. Contains the result of a :meth:`RTMP.call`. """ def __init__(self, conn, transaction_id): self._result = None self.conn = conn self.done = False self.transaction_id = transaction_id def result(self, timeout=None): """Retrieves the result of the call. :param timeout: The time to wait for a result from the server. Raises :exc:`RTMPTimeoutError` on timeout. """ if self.done: return self._result result = self.conn.process_packets(transaction_id=self.transaction_id, timeout=timeout) self._result = result self.done = True return result python-librtmp-0.3.0/librtmp/exceptions.py0000644000175000017500000000026112527415446022015 0ustar chrippachrippa00000000000000__all__ = ["AMFError", "RTMPError", "RTMPTimeoutError"] class AMFError(Exception): pass class RTMPError(IOError): pass class RTMPTimeoutError(RTMPError): pass python-librtmp-0.3.0/librtmp/utils.py0000644000175000017500000000300312527415446020771 0ustar chrippachrippa00000000000000from binascii import hexlify from collections import namedtuple from . import ffi, librtmp from .aval import AVal from .compat import bytes, str from .exceptions import RTMPError __all__ = ["add_signal_handler", "hash_swf"] def add_signal_handler(): """Adds a signal handler to handle KeyboardInterrupt.""" import signal def handler(sig, frame): if sig == signal.SIGINT: librtmp.RTMP_UserInterrupt() raise KeyboardInterrupt signal.signal(signal.SIGINT, handler) def hash_swf(url, age=30): hash = ffi.new("unsigned char[]", 32) size = ffi.new("unsigned int*") url = bytes(url, "utf8") res = librtmp.RTMP_HashSWF(url, size, hash, age) if res == 0: hash = hexlify(ffi.buffer(hash, 32)[:]) size = size[0] return str(hash, "utf8"), size else: raise RTMPError("Failed to hash SWF") RTMPURL = namedtuple("RTMPURL", ["protocol", "hostname", "port", "playpath", "app"]) def parse_url(url): protocol = ffi.new("int*") hostname = AVal("") port = ffi.new("unsigned int*") playpath = AVal("") app = AVal("") res = librtmp.RTMP_ParseURL(bytes(url, "utf8"), protocol, hostname.aval, port, playpath.aval, app.aval) if res < 1: result = RTMPURL(0, "", 0, "", "") else: result = RTMPURL(protocol[0], str(hostname.value, "utf8"), port[0], str(playpath.value, "utf8"), str(app.value, "utf8")) return result python-librtmp-0.3.0/librtmp/ffi.py0000644000175000017500000001260412527415446020404 0ustar chrippachrippa00000000000000from cffi import FFI __all__ = ["ffi"] ffi = FFI() preamble = """ #include #include /* The librtmp API is somewhat lacking. These functions help a bit. */ void RTMP_SetSWFHash(RTMP *r, const char *swfhash, uint32_t swfsize) { if (swfhash != NULL && swfsize > 0) { memcpy(r->Link.SWFHash, swfhash, sizeof(r->Link.SWFHash)); r->Link.SWFSize = swfsize; } else { r->Link.SWFSize = 0; } } int RTMP_GetInvokeCount(RTMP *r) { return r->m_numInvokes; } void RTMP_SetInvokeCount(RTMP *r, int count) { r->m_numInvokes = count; } /* Logging */ #include void (*python_log_callback)(int level, char *msg); void c_log_callback(int level, const char *fmt, va_list args) { char buf[2048]; vsprintf(buf, fmt, args); python_log_callback(level, buf); } """ ffi.cdef(""" typedef struct AVal { char *av_val; int av_len; } AVal; typedef ... RTMP; typedef ... RTMPChunk; typedef int RTMP_LogLevel; typedef void* va_list; typedef void (RTMP_LogCallback)(int level, const char *fmt, va_list arg); typedef struct RTMPPacket { uint8_t m_headerType; uint8_t m_packetType; uint8_t m_hasAbsTimestamp; int m_nChannel; uint32_t m_nTimeStamp; int32_t m_nInfoField2; uint32_t m_nBodySize; uint32_t m_nBytesRead; RTMPChunk *m_chunk; char *m_body; } RTMPPacket; RTMP * RTMP_Alloc(void); void RTMP_Init(RTMP *r); int RTMP_SetupURL(RTMP *r, char *url); int RTMP_ParseURL(const char *url, int *protocol, AVal *host, unsigned int *port, AVal *playpath, AVal *app); int RTMP_Connect(RTMP *r, RTMPPacket *cp); int RTMP_Serve(RTMP *r); int RTMP_IsConnected(RTMP *r); int RTMP_Socket(RTMP *r); int RTMP_IsTimedout(RTMP *r); int RTMP_ReadPacket(RTMP *r, RTMPPacket *packet); int RTMP_SendPacket(RTMP *r, RTMPPacket *packet, int queue); int RTMP_ClientPacket(RTMP *r, RTMPPacket *packet); int RTMP_ConnectStream(RTMP *r, int seek); int RTMP_ReconnectStream(RTMP *r, int seekTime); int RTMP_SendCreateStream(RTMP *r); void RTMP_DeleteStream(RTMP *r); int RTMP_Read(RTMP *r, char *buf, int size); int RTMP_Write(RTMP *r, const char *buf, int size); int RTMP_Pause(RTMP *r, int doPause); int RTMP_SendSeek(RTMP *r, int dTime); void RTMP_SetBufferMS(RTMP *r, int size); void RTMP_UpdateBufferMS(RTMP *r); int RTMP_LibVersion(void); int RTMP_SetOpt(RTMP *r, const AVal *opt, AVal *arg); double RTMP_GetDuration(RTMP *r); void RTMP_EnableWrite(RTMP *r); void RTMP_Close(RTMP *r); void RTMP_Free(RTMP *r); void RTMPPacket_Reset(RTMPPacket *p); void RTMPPacket_Dump(RTMPPacket *p); int RTMPPacket_Alloc(RTMPPacket *p, int nSize); void RTMPPacket_Free(RTMPPacket *p); RTMP_LogLevel RTMP_LogGetLevel(void); void RTMP_LogSetLevel(RTMP_LogLevel lvl); void RTMP_UserInterrupt(void); void RTMP_LogSetCallback(RTMP_LogCallback *cb); int RTMP_HashSWF(const char *url, unsigned int *size, unsigned char *hash, int age); void RTMP_SetSWFHash(RTMP *r, const char *swfhash, uint32_t swfsize); int RTMP_GetInvokeCount(RTMP *r); void RTMP_SetInvokeCount(RTMP *r, int count); typedef enum { AMF_NUMBER = 0, AMF_BOOLEAN, AMF_STRING, AMF_OBJECT, AMF_MOVIECLIP, /* reserved, not used */ AMF_NULL, AMF_UNDEFINED, AMF_REFERENCE, AMF_ECMA_ARRAY, AMF_OBJECT_END, AMF_STRICT_ARRAY, AMF_DATE, AMF_LONG_STRING, AMF_UNSUPPORTED, AMF_RECORDSET, /* reserved, not used */ AMF_XML_DOC, AMF_TYPED_OBJECT, AMF_AVMPLUS, /* switch to AMF3 */ AMF_INVALID = 0xff } AMFDataType; typedef ... AMFObjectProperty; typedef struct AMFObject { ...; } AMFObject; void AMF_Dump(AMFObject * obj); char *AMF_EncodeString(char *output, char *outend, const AVal * str); char *AMF_EncodeNumber(char *output, char *outend, double dVal); char *AMF_EncodeBoolean(char *output, char *outend, int bVal); char *AMF_EncodeInt16(char *output, char *outend, short nVal); char *AMF_EncodeInt24(char *output, char *outend, int nVal); char *AMF_EncodeInt32(char *output, char *outend, int nVal); int AMF_Decode(AMFObject * obj, const char *pBuffer, int nSize, int bDecodeName); int AMF_CountProp(AMFObject * obj); AMFObjectProperty *AMF_GetProp(AMFObject * obj, const AVal * name, int nIndex); AMFDataType AMFProp_GetType(AMFObjectProperty * prop); void AMFProp_GetName(AMFObjectProperty * prop, AVal * name); void AMFProp_SetName(AMFObjectProperty * prop, AVal * name); double AMFProp_GetNumber(AMFObjectProperty * prop); int AMFProp_GetBoolean(AMFObjectProperty * prop); void AMFProp_GetString(AMFObjectProperty * prop, AVal * str); void AMFProp_GetObject(AMFObjectProperty * prop, AMFObject * obj); int AMFProp_IsValid(AMFObjectProperty * prop); void (*python_log_callback)(int level, char *msg); void *const c_log_callback; """) ffi.set_source("librtmp._librtmp", preamble, libraries=["rtmp"], sources=["src/librtmp/amf.c"]) python-librtmp-0.3.0/setup.cfg0000644000175000017500000000026212530710447017424 0ustar chrippachrippa00000000000000[build_sphinx] source-dir = docs build-dir = docs/_build all_files = 1 [upload_sphinx] upload-dir = docs/_build/html [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 python-librtmp-0.3.0/LICENSE0000644000175000017500000000243112143032304016575 0ustar chrippachrippa00000000000000Copyright (c) 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. python-librtmp-0.3.0/tests/0000755000175000017500000000000012530710447016745 5ustar chrippachrippa00000000000000python-librtmp-0.3.0/tests/test_rtmp.py0000644000175000017500000000072012530341565021340 0ustar chrippachrippa00000000000000from librtmp import RTMP, RTMPError from pytest import raises def test_connect(): with raises(RTMPError): conn = RTMP("rtmp://localhost/app/playpath", live=True) conn.connect() def test_remote_method(): conn = RTMP("rtmp://localhost/app/playpath", live=True) my_remote_method = conn.remote_method("MyRemoteMethod", block=True) assert callable(my_remote_method) == True assert my_remote_method.__name__ == "MyRemoteMethod" python-librtmp-0.3.0/README.rst0000644000175000017500000000670412530710115017271 0ustar chrippachrippa00000000000000python-librtmp ============== .. image:: http://img.shields.io/pypi/v/python-librtmp.svg?style=flat-square :target: https://pypi.python.org/pypi/python-librtmp .. image:: http://img.shields.io/pypi/dm/python-librtmp.svg?style=flat-square :target: https://pypi.python.org/pypi/python-librtmp .. image:: http://img.shields.io/travis/chrippa/python-librtmp.svg?style=flat-square :target: http://travis-ci.org/chrippa/python-librtmp python-librtmp is a `RTMP`_ client library. It uses the implementation provided by `librtmp`_ via `cffi`_. * Free software: `BSD license`_ * Documentation: http://pythonhosted.org/python-librtmp .. _RTMP: http://en.wikipedia.org/wiki/Real_Time_Messaging_Protocol .. _cffi: http://cffi.readthedocs.org/ .. _librtmp: http://rtmpdump.mplayerhq.hu/librtmp.3.html .. _BSD license: http://opensource.org/licenses/BSD-2-Clause Installation ============ The latest stable version is available to install using `pip`_ .. code-block:: console sudo pip install python-librtmp But you can also get the development version using `Git `_: .. code-block:: console git clone git://github.com/chrippa/python-librtmp.git cd python-librtmp sudo python setup.py install .. _pip: http://pip-installer.org/ .. _git: http://git-scm.com/ Dependencies ------------ - `Python`_, at least version 2.6 or 3.3. - a C compiler capapable of building `Python`_ extensions, e.g. `gcc`_ - `librtmp`_: The library including its headers (`librtmp-dev` or equivalent) - `cffi`_: cffi depends on libffi and its headers (`libffi-dev` or equivalent) - On Python <3.4 the backport of `singledispatch`_ is also required. .. _gcc: https://gcc.gnu.org/ .. _python: http://python.org/ .. _singledispatch: https://pypi.python.org/pypi/singledispatch Windows ------- python-librtmp (and `cffi`_) has wheel packages (binaries) available on PyPi and can therefore be easily installed with `pip 1.4+ `_ without the need to compile anything: .. code-block:: console > pip install python-librtmp (on older pip versions you need to use --use-wheel) > pip install --use-wheel python-librtmp Features ======== Streaming --------- The most common use case of RTMP is to read a video stream from a server. .. code-block:: python import librtmp # Create a connection conn = librtmp.RTMP("rtmp://your.server.net/app/playpath", live=True) # Attempt to connect conn.connect() # Get a file-like object to access to the stream stream = conn.create_stream() # Read 1024 bytes of data data = stream.read(1024) Remote function calls --------------------- Here is a example of creating a Python function that can be used to call remote functions: .. code-block:: python my_remote_method = conn.remote_method("MyRemoteMethod", block=True) result = my_remote_method("some argument") Waiting for the server to call our function: .. code-block:: python # This will automatically name the function after it's Python name @conn.invoke_handler def my_add(a, b): return a + b # Start waiting for calls conn.process_packets() You can also use custom function name instead: .. code-block:: python @conn.invoke_handler("MyMath.MyAdd") Instead of blocking forever when waiting for a call you can specify to wait only for a specific invoke and then stop blocking: .. code-block:: python conn.process_packets(invoked_method="MyMath.MyAdd", timeout=30) python-librtmp-0.3.0/PKG-INFO0000644000175000017500000001536112530710447016706 0ustar chrippachrippa00000000000000Metadata-Version: 1.1 Name: python-librtmp Version: 0.3.0 Summary: Python bindings for librtmp, built with cffi Home-page: https://github.com/chrippa/python-librtmp Author: Christopher Rosell Author-email: chrippa@tanuki.se License: Simplified BSD Description: python-librtmp ============== .. image:: http://img.shields.io/pypi/v/python-librtmp.svg?style=flat-square :target: https://pypi.python.org/pypi/python-librtmp .. image:: http://img.shields.io/pypi/dm/python-librtmp.svg?style=flat-square :target: https://pypi.python.org/pypi/python-librtmp .. image:: http://img.shields.io/travis/chrippa/python-librtmp.svg?style=flat-square :target: http://travis-ci.org/chrippa/python-librtmp python-librtmp is a `RTMP`_ client library. It uses the implementation provided by `librtmp`_ via `cffi`_. * Free software: `BSD license`_ * Documentation: http://pythonhosted.org/python-librtmp .. _RTMP: http://en.wikipedia.org/wiki/Real_Time_Messaging_Protocol .. _cffi: http://cffi.readthedocs.org/ .. _librtmp: http://rtmpdump.mplayerhq.hu/librtmp.3.html .. _BSD license: http://opensource.org/licenses/BSD-2-Clause Installation ============ The latest stable version is available to install using `pip`_ .. code-block:: console sudo pip install python-librtmp But you can also get the development version using `Git `_: .. code-block:: console git clone git://github.com/chrippa/python-librtmp.git cd python-librtmp sudo python setup.py install .. _pip: http://pip-installer.org/ .. _git: http://git-scm.com/ Dependencies ------------ - `Python`_, at least version 2.6 or 3.3. - a C compiler capapable of building `Python`_ extensions, e.g. `gcc`_ - `librtmp`_: The library including its headers (`librtmp-dev` or equivalent) - `cffi`_: cffi depends on libffi and its headers (`libffi-dev` or equivalent) - On Python <3.4 the backport of `singledispatch`_ is also required. .. _gcc: https://gcc.gnu.org/ .. _python: http://python.org/ .. _singledispatch: https://pypi.python.org/pypi/singledispatch Windows ------- python-librtmp (and `cffi`_) has wheel packages (binaries) available on PyPi and can therefore be easily installed with `pip 1.4+ `_ without the need to compile anything: .. code-block:: console > pip install python-librtmp (on older pip versions you need to use --use-wheel) > pip install --use-wheel python-librtmp Features ======== Streaming --------- The most common use case of RTMP is to read a video stream from a server. .. code-block:: python import librtmp # Create a connection conn = librtmp.RTMP("rtmp://your.server.net/app/playpath", live=True) # Attempt to connect conn.connect() # Get a file-like object to access to the stream stream = conn.create_stream() # Read 1024 bytes of data data = stream.read(1024) Remote function calls --------------------- Here is a example of creating a Python function that can be used to call remote functions: .. code-block:: python my_remote_method = conn.remote_method("MyRemoteMethod", block=True) result = my_remote_method("some argument") Waiting for the server to call our function: .. code-block:: python # This will automatically name the function after it's Python name @conn.invoke_handler def my_add(a, b): return a + b # Start waiting for calls conn.process_packets() You can also use custom function name instead: .. code-block:: python @conn.invoke_handler("MyMath.MyAdd") Instead of blocking forever when waiting for a call you can specify to wait only for a specific invoke and then stop blocking: .. code-block:: python conn.process_packets(invoked_method="MyMath.MyAdd", timeout=30) History ------- 0.3.0 (2015-05-25) ^^^^^^^^^^^^^^^^^^ * Added update_buffer option (enabled by default) to RTMP.create_stream, which enables a hack to increase throughput. * Added a update_buffer method to RTMPStream. * We now require at least version 1.0.1 of cffi. 0.2.2 (2015-04-15) ^^^^^^^^^^^^^^^^^^ * Fixed proxy not being used by librtmp. * Added support for Cygwin, patch by @schrobby. (#17) 0.2.1 (2014-09-01) ^^^^^^^^^^^^^^^^^^ * Fixed expected bytes type on Python 2. * Fixed singledispatch dependency condition. 0.2.0 (2014-04-07) ^^^^^^^^^^^^^^^^^^ * RTMPError now inherits from IOError. * Fixed MSVC build. * Added librtmp.so.1 to library paths, patch by Athanasios Oikonomou. (#4) * Added librtmp.dylib to library paths, patch by Will Donohoe. (#6) 0.1.2 (2013-10-08) ^^^^^^^^^^^^^^^^^^ * Fixed compilation issue on some platforms. * Fixed AMF issue on older librtmp versions. (#1) 0.1.1 (2013-09-25) ^^^^^^^^^^^^^^^^^^ * Fixed packaging issues. 0.1.0 (2013-09-23) ^^^^^^^^^^^^^^^^^^ * First release on PyPI. Keywords: python-librtmp Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Natural Language :: English Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 python-librtmp-0.3.0/python_librtmp.egg-info/0000755000175000017500000000000012530710447022347 5ustar chrippachrippa00000000000000python-librtmp-0.3.0/python_librtmp.egg-info/dependency_links.txt0000644000175000017500000000000112530710446026414 0ustar chrippachrippa00000000000000 python-librtmp-0.3.0/python_librtmp.egg-info/top_level.txt0000644000175000017500000000001012530710446025067 0ustar chrippachrippa00000000000000librtmp python-librtmp-0.3.0/python_librtmp.egg-info/requires.txt0000644000175000017500000000001412530710446024741 0ustar chrippachrippa00000000000000cffi>=1.0.1 python-librtmp-0.3.0/python_librtmp.egg-info/PKG-INFO0000644000175000017500000001536112530710446023451 0ustar chrippachrippa00000000000000Metadata-Version: 1.1 Name: python-librtmp Version: 0.3.0 Summary: Python bindings for librtmp, built with cffi Home-page: https://github.com/chrippa/python-librtmp Author: Christopher Rosell Author-email: chrippa@tanuki.se License: Simplified BSD Description: python-librtmp ============== .. image:: http://img.shields.io/pypi/v/python-librtmp.svg?style=flat-square :target: https://pypi.python.org/pypi/python-librtmp .. image:: http://img.shields.io/pypi/dm/python-librtmp.svg?style=flat-square :target: https://pypi.python.org/pypi/python-librtmp .. image:: http://img.shields.io/travis/chrippa/python-librtmp.svg?style=flat-square :target: http://travis-ci.org/chrippa/python-librtmp python-librtmp is a `RTMP`_ client library. It uses the implementation provided by `librtmp`_ via `cffi`_. * Free software: `BSD license`_ * Documentation: http://pythonhosted.org/python-librtmp .. _RTMP: http://en.wikipedia.org/wiki/Real_Time_Messaging_Protocol .. _cffi: http://cffi.readthedocs.org/ .. _librtmp: http://rtmpdump.mplayerhq.hu/librtmp.3.html .. _BSD license: http://opensource.org/licenses/BSD-2-Clause Installation ============ The latest stable version is available to install using `pip`_ .. code-block:: console sudo pip install python-librtmp But you can also get the development version using `Git `_: .. code-block:: console git clone git://github.com/chrippa/python-librtmp.git cd python-librtmp sudo python setup.py install .. _pip: http://pip-installer.org/ .. _git: http://git-scm.com/ Dependencies ------------ - `Python`_, at least version 2.6 or 3.3. - a C compiler capapable of building `Python`_ extensions, e.g. `gcc`_ - `librtmp`_: The library including its headers (`librtmp-dev` or equivalent) - `cffi`_: cffi depends on libffi and its headers (`libffi-dev` or equivalent) - On Python <3.4 the backport of `singledispatch`_ is also required. .. _gcc: https://gcc.gnu.org/ .. _python: http://python.org/ .. _singledispatch: https://pypi.python.org/pypi/singledispatch Windows ------- python-librtmp (and `cffi`_) has wheel packages (binaries) available on PyPi and can therefore be easily installed with `pip 1.4+ `_ without the need to compile anything: .. code-block:: console > pip install python-librtmp (on older pip versions you need to use --use-wheel) > pip install --use-wheel python-librtmp Features ======== Streaming --------- The most common use case of RTMP is to read a video stream from a server. .. code-block:: python import librtmp # Create a connection conn = librtmp.RTMP("rtmp://your.server.net/app/playpath", live=True) # Attempt to connect conn.connect() # Get a file-like object to access to the stream stream = conn.create_stream() # Read 1024 bytes of data data = stream.read(1024) Remote function calls --------------------- Here is a example of creating a Python function that can be used to call remote functions: .. code-block:: python my_remote_method = conn.remote_method("MyRemoteMethod", block=True) result = my_remote_method("some argument") Waiting for the server to call our function: .. code-block:: python # This will automatically name the function after it's Python name @conn.invoke_handler def my_add(a, b): return a + b # Start waiting for calls conn.process_packets() You can also use custom function name instead: .. code-block:: python @conn.invoke_handler("MyMath.MyAdd") Instead of blocking forever when waiting for a call you can specify to wait only for a specific invoke and then stop blocking: .. code-block:: python conn.process_packets(invoked_method="MyMath.MyAdd", timeout=30) History ------- 0.3.0 (2015-05-25) ^^^^^^^^^^^^^^^^^^ * Added update_buffer option (enabled by default) to RTMP.create_stream, which enables a hack to increase throughput. * Added a update_buffer method to RTMPStream. * We now require at least version 1.0.1 of cffi. 0.2.2 (2015-04-15) ^^^^^^^^^^^^^^^^^^ * Fixed proxy not being used by librtmp. * Added support for Cygwin, patch by @schrobby. (#17) 0.2.1 (2014-09-01) ^^^^^^^^^^^^^^^^^^ * Fixed expected bytes type on Python 2. * Fixed singledispatch dependency condition. 0.2.0 (2014-04-07) ^^^^^^^^^^^^^^^^^^ * RTMPError now inherits from IOError. * Fixed MSVC build. * Added librtmp.so.1 to library paths, patch by Athanasios Oikonomou. (#4) * Added librtmp.dylib to library paths, patch by Will Donohoe. (#6) 0.1.2 (2013-10-08) ^^^^^^^^^^^^^^^^^^ * Fixed compilation issue on some platforms. * Fixed AMF issue on older librtmp versions. (#1) 0.1.1 (2013-09-25) ^^^^^^^^^^^^^^^^^^ * Fixed packaging issues. 0.1.0 (2013-09-23) ^^^^^^^^^^^^^^^^^^ * First release on PyPI. Keywords: python-librtmp Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Natural Language :: English Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 python-librtmp-0.3.0/python_librtmp.egg-info/SOURCES.txt0000644000175000017500000000114312530710447024232 0ustar chrippachrippa00000000000000AUTHORS.rst CONTRIBUTING.rst HISTORY.rst LICENSE MANIFEST.in README.rst setup.cfg setup.py tox.ini librtmp/__init__.py librtmp/amf.py librtmp/aval.py librtmp/compat.py librtmp/exceptions.py librtmp/ffi.py librtmp/logging.py librtmp/packet.py librtmp/rtmp.py librtmp/stream.py librtmp/utils.py python_librtmp.egg-info/PKG-INFO python_librtmp.egg-info/SOURCES.txt python_librtmp.egg-info/dependency_links.txt python_librtmp.egg-info/not-zip-safe python_librtmp.egg-info/requires.txt python_librtmp.egg-info/top_level.txt src/librtmp/amf.c src/librtmp/amf.h src/librtmp/bytes.h src/librtmp/log.h tests/test_rtmp.pypython-librtmp-0.3.0/python_librtmp.egg-info/not-zip-safe0000644000175000017500000000000112224635367024604 0ustar chrippachrippa00000000000000 python-librtmp-0.3.0/tox.ini0000644000175000017500000000034712530702570017120 0ustar chrippachrippa00000000000000[tox] envlist = py26, py27, py33, py34 [testenv:py26] install_command = pip install --no-use-wheel {opts} {packages} [testenv] deps= pytest>=pytest-2.2 wheel commands = py.test tests python setup.py bdist_wheel python-librtmp-0.3.0/CONTRIBUTING.rst0000644000175000017500000000506612216372033020247 0ustar chrippachrippa00000000000000============ Contributing ============ Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given. You can contribute in many ways: Types of Contributions ---------------------- Report Bugs ~~~~~~~~~~~ Report bugs at https://github.com/chrippa/python-librtmp/issues. If you are reporting a bug, please include: * Your operating system name and version. * Any details about your local setup that might be helpful in troubleshooting. * Detailed steps to reproduce the bug. Fix Bugs ~~~~~~~~ Look through the GitHub issues for bugs. Anything tagged with "bug" is open to whoever wants to implement it. Implement Features ~~~~~~~~~~~~~~~~~~ Look through the GitHub issues for features. Anything tagged with "feature" is open to whoever wants to implement it. Write Documentation ~~~~~~~~~~~~~~~~~~~ python-librtmp could always use more documentation, whether as part of the official python-librtmp docs, in docstrings, or even on the web in blog posts, articles, and such. Submit Feedback ~~~~~~~~~~~~~~~ The best way to send feedback is to file an issue at https://github.com/chrippa/python-librtmp/issues. If you are proposing a feature: * Explain in detail how it would work. * Keep the scope as narrow as possible, to make it easier to implement. * Remember that this is a volunteer-driven project, and that contributions are welcome :) Get Started! ------------ Ready to contribute? Here's how to set up `python-librtmp` for local development. 1. Fork the `python-librtmp` repo on GitHub. 2. Clone your fork locally:: $ git clone git@github.com:your_name_here/python-librtmp.git 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: $ mkvirtualenv python-librtmp $ cd python-librtmp/ $ python setup.py develop 4. Create a branch for local development:: $ git checkout -b name-of-your-bugfix-or-feature Now you can make your changes locally. 5. Commit your changes and push your branch to GitHub:: $ git add . $ git commit -m "Your detailed description of your changes." $ git push origin name-of-your-bugfix-or-feature 6. Submit a pull request through the GitHub website. Pull Request Guidelines ----------------------- Before you submit a pull request, check that it meets these guidelines: 1. If the pull request adds functionality, the docs should be updated. Put your new functionality into a function with a docstring. 2. The pull request should work for Python 2.6, 2.7, and 3.3. python-librtmp-0.3.0/setup.py0000644000175000017500000000246012530710444017314 0ustar chrippachrippa00000000000000#!/usr/bin/env python from setuptools import setup from sys import version_info install_requires = ["cffi>=1.0.1"] if not (version_info[0] == 3 and version_info[1] >= 4): install_requires.append("singledispatch") readme = open("README.rst").read() history = open("HISTORY.rst").read().replace(".. :changelog:", "") setup( name="python-librtmp", version="0.3.0", description="Python bindings for librtmp, built with cffi", long_description=readme + "\n\n" + history, author="Christopher Rosell", author_email="chrippa@tanuki.se", url="https://github.com/chrippa/python-librtmp", license="Simplified BSD", packages=["librtmp"], cffi_modules=["librtmp/ffi.py:ffi"], install_requires=install_requires, setup_requires=["cffi>=1.0.1"], zip_safe=False, keywords="python-librtmp", classifiers=[ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Natural Language :: English", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", ] ) python-librtmp-0.3.0/src/0000755000175000017500000000000012530710447016372 5ustar chrippachrippa00000000000000python-librtmp-0.3.0/src/librtmp/0000755000175000017500000000000012530710447020043 5ustar chrippachrippa00000000000000python-librtmp-0.3.0/src/librtmp/amf.h0000644000175000017500000001276612224632436020774 0ustar chrippachrippa00000000000000#ifndef __AMF_H__ #define __AMF_H__ /* * Copyright (C) 2005-2008 Team XBMC * http://www.xbmc.org * Copyright (C) 2008-2009 Andrej Stepanchuk * Copyright (C) 2009-2010 Howard Chu * * This file is part of librtmp. * * librtmp is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1, * or (at your option) any later version. * * librtmp is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with librtmp see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/lgpl.html */ #include #ifndef TRUE #define TRUE 1 #define FALSE 0 #endif #ifdef __cplusplus extern "C" { #endif typedef enum { AMF_NUMBER = 0, AMF_BOOLEAN, AMF_STRING, AMF_OBJECT, AMF_MOVIECLIP, /* reserved, not used */ AMF_NULL, AMF_UNDEFINED, AMF_REFERENCE, AMF_ECMA_ARRAY, AMF_OBJECT_END, AMF_STRICT_ARRAY, AMF_DATE, AMF_LONG_STRING, AMF_UNSUPPORTED, AMF_RECORDSET, /* reserved, not used */ AMF_XML_DOC, AMF_TYPED_OBJECT, AMF_AVMPLUS, /* switch to AMF3 */ AMF_INVALID = 0xff } AMFDataType; typedef enum { AMF3_UNDEFINED = 0, AMF3_NULL, AMF3_FALSE, AMF3_TRUE, AMF3_INTEGER, AMF3_DOUBLE, AMF3_STRING, AMF3_XML_DOC, AMF3_DATE, AMF3_ARRAY, AMF3_OBJECT, AMF3_XML, AMF3_BYTE_ARRAY } AMF3DataType; typedef struct AVal { char *av_val; int av_len; } AVal; #define AVC(str) {str,sizeof(str)-1} #define AVMATCH(a1,a2) ((a1)->av_len == (a2)->av_len && !memcmp((a1)->av_val,(a2)->av_val,(a1)->av_len)) struct AMFObjectProperty; typedef struct AMFObject { int o_num; struct AMFObjectProperty *o_props; } AMFObject; typedef struct AMFObjectProperty { AVal p_name; AMFDataType p_type; union { double p_number; AVal p_aval; AMFObject p_object; } p_vu; int16_t p_UTCoffset; } AMFObjectProperty; char *AMF_EncodeString(char *output, char *outend, const AVal * str); char *AMF_EncodeNumber(char *output, char *outend, double dVal); char *AMF_EncodeInt16(char *output, char *outend, short nVal); char *AMF_EncodeInt24(char *output, char *outend, int nVal); char *AMF_EncodeInt32(char *output, char *outend, int nVal); char *AMF_EncodeBoolean(char *output, char *outend, int bVal); /* Shortcuts for AMFProp_Encode */ char *AMF_EncodeNamedString(char *output, char *outend, const AVal * name, const AVal * value); char *AMF_EncodeNamedNumber(char *output, char *outend, const AVal * name, double dVal); char *AMF_EncodeNamedBoolean(char *output, char *outend, const AVal * name, int bVal); unsigned short AMF_DecodeInt16(const char *data); unsigned int AMF_DecodeInt24(const char *data); unsigned int AMF_DecodeInt32(const char *data); void AMF_DecodeString(const char *data, AVal * str); void AMF_DecodeLongString(const char *data, AVal * str); int AMF_DecodeBoolean(const char *data); double AMF_DecodeNumber(const char *data); char *AMF_Encode(AMFObject * obj, char *pBuffer, char *pBufEnd); char *AMF_EncodeEcmaArray(AMFObject *obj, char *pBuffer, char *pBufEnd); char *AMF_EncodeArray(AMFObject *obj, char *pBuffer, char *pBufEnd); int AMF_Decode(AMFObject * obj, const char *pBuffer, int nSize, int bDecodeName); int AMF_DecodeArray(AMFObject * obj, const char *pBuffer, int nSize, int nArrayLen, int bDecodeName); int AMF3_Decode(AMFObject * obj, const char *pBuffer, int nSize, int bDecodeName); void AMF_Dump(AMFObject * obj); void AMF_Reset(AMFObject * obj); void AMF_AddProp(AMFObject * obj, const AMFObjectProperty * prop); int AMF_CountProp(AMFObject * obj); AMFObjectProperty *AMF_GetProp(AMFObject * obj, const AVal * name, int nIndex); AMFDataType AMFProp_GetType(AMFObjectProperty * prop); void AMFProp_SetNumber(AMFObjectProperty * prop, double dval); void AMFProp_SetBoolean(AMFObjectProperty * prop, int bflag); void AMFProp_SetString(AMFObjectProperty * prop, AVal * str); void AMFProp_SetObject(AMFObjectProperty * prop, AMFObject * obj); void AMFProp_GetName(AMFObjectProperty * prop, AVal * name); void AMFProp_SetName(AMFObjectProperty * prop, AVal * name); double AMFProp_GetNumber(AMFObjectProperty * prop); int AMFProp_GetBoolean(AMFObjectProperty * prop); void AMFProp_GetString(AMFObjectProperty * prop, AVal * str); void AMFProp_GetObject(AMFObjectProperty * prop, AMFObject * obj); int AMFProp_IsValid(AMFObjectProperty * prop); char *AMFProp_Encode(AMFObjectProperty * prop, char *pBuffer, char *pBufEnd); int AMF3Prop_Decode(AMFObjectProperty * prop, const char *pBuffer, int nSize, int bDecodeName); int AMFProp_Decode(AMFObjectProperty * prop, const char *pBuffer, int nSize, int bDecodeName); void AMFProp_Dump(AMFObjectProperty * prop); void AMFProp_Reset(AMFObjectProperty * prop); typedef struct AMF3ClassDef { AVal cd_name; char cd_externalizable; char cd_dynamic; int cd_num; AVal *cd_props; } AMF3ClassDef; void AMF3CD_AddProp(AMF3ClassDef * cd, AVal * prop); AVal *AMF3CD_GetProp(AMF3ClassDef * cd, int idx); #ifdef __cplusplus } #endif #endif /* __AMF_H__ */ python-librtmp-0.3.0/src/librtmp/amf.c0000700000175000017500000006370512232525507020756 0ustar chrippachrippa00000000000000/* * Copyright (C) 2005-2008 Team XBMC * http://www.xbmc.org * Copyright (C) 2008-2009 Andrej Stepanchuk * Copyright (C) 2009-2010 Howard Chu * * This file is part of librtmp. * * librtmp is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1, * or (at your option) any later version. * * librtmp is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with librtmp see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/lgpl.html */ #include #include #include #include "amf.h" #include "log.h" #include "bytes.h" #if _MSC_VER #define snprintf _snprintf #endif static const AMFObjectProperty AMFProp_Invalid = { {0, 0}, AMF_INVALID }; static const AVal AV_empty = { 0, 0 }; /* Data is Big-Endian */ unsigned short AMF_DecodeInt16(const char *data) { unsigned char *c = (unsigned char *) data; unsigned short val; val = (c[0] << 8) | c[1]; return val; } unsigned int AMF_DecodeInt24(const char *data) { unsigned char *c = (unsigned char *) data; unsigned int val; val = (c[0] << 16) | (c[1] << 8) | c[2]; return val; } unsigned int AMF_DecodeInt32(const char *data) { unsigned char *c = (unsigned char *)data; unsigned int val; val = (c[0] << 24) | (c[1] << 16) | (c[2] << 8) | c[3]; return val; } void AMF_DecodeString(const char *data, AVal *bv) { bv->av_len = AMF_DecodeInt16(data); bv->av_val = (bv->av_len > 0) ? (char *)data + 2 : NULL; } void AMF_DecodeLongString(const char *data, AVal *bv) { bv->av_len = AMF_DecodeInt32(data); bv->av_val = (bv->av_len > 0) ? (char *)data + 4 : NULL; } double AMF_DecodeNumber(const char *data) { double dVal; #if __FLOAT_WORD_ORDER == __BYTE_ORDER #if __BYTE_ORDER == __BIG_ENDIAN memcpy(&dVal, data, 8); #elif __BYTE_ORDER == __LITTLE_ENDIAN unsigned char *ci, *co; ci = (unsigned char *)data; co = (unsigned char *)&dVal; co[0] = ci[7]; co[1] = ci[6]; co[2] = ci[5]; co[3] = ci[4]; co[4] = ci[3]; co[5] = ci[2]; co[6] = ci[1]; co[7] = ci[0]; #endif #else #if __BYTE_ORDER == __LITTLE_ENDIAN /* __FLOAT_WORD_ORER == __BIG_ENDIAN */ unsigned char *ci, *co; ci = (unsigned char *)data; co = (unsigned char *)&dVal; co[0] = ci[3]; co[1] = ci[2]; co[2] = ci[1]; co[3] = ci[0]; co[4] = ci[7]; co[5] = ci[6]; co[6] = ci[5]; co[7] = ci[4]; #else /* __BYTE_ORDER == __BIG_ENDIAN && __FLOAT_WORD_ORER == __LITTLE_ENDIAN */ unsigned char *ci, *co; ci = (unsigned char *)data; co = (unsigned char *)&dVal; co[0] = ci[4]; co[1] = ci[5]; co[2] = ci[6]; co[3] = ci[7]; co[4] = ci[0]; co[5] = ci[1]; co[6] = ci[2]; co[7] = ci[3]; #endif #endif return dVal; } int AMF_DecodeBoolean(const char *data) { return *data != 0; } char * AMF_EncodeInt16(char *output, char *outend, short nVal) { if (output+2 > outend) return NULL; output[1] = nVal & 0xff; output[0] = nVal >> 8; return output+2; } char * AMF_EncodeInt24(char *output, char *outend, int nVal) { if (output+3 > outend) return NULL; output[2] = nVal & 0xff; output[1] = nVal >> 8; output[0] = nVal >> 16; return output+3; } char * AMF_EncodeInt32(char *output, char *outend, int nVal) { if (output+4 > outend) return NULL; output[3] = nVal & 0xff; output[2] = nVal >> 8; output[1] = nVal >> 16; output[0] = nVal >> 24; return output+4; } char * AMF_EncodeString(char *output, char *outend, const AVal *bv) { if ((bv->av_len < 65536 && output + 1 + 2 + bv->av_len > outend) || output + 1 + 4 + bv->av_len > outend) return NULL; if (bv->av_len < 65536) { *output++ = AMF_STRING; output = AMF_EncodeInt16(output, outend, bv->av_len); } else { *output++ = AMF_LONG_STRING; output = AMF_EncodeInt32(output, outend, bv->av_len); } memcpy(output, bv->av_val, bv->av_len); output += bv->av_len; return output; } char * AMF_EncodeNumber(char *output, char *outend, double dVal) { if (output+1+8 > outend) return NULL; *output++ = AMF_NUMBER; /* type: Number */ #if __FLOAT_WORD_ORDER == __BYTE_ORDER #if __BYTE_ORDER == __BIG_ENDIAN memcpy(output, &dVal, 8); #elif __BYTE_ORDER == __LITTLE_ENDIAN { unsigned char *ci, *co; ci = (unsigned char *)&dVal; co = (unsigned char *)output; co[0] = ci[7]; co[1] = ci[6]; co[2] = ci[5]; co[3] = ci[4]; co[4] = ci[3]; co[5] = ci[2]; co[6] = ci[1]; co[7] = ci[0]; } #endif #else #if __BYTE_ORDER == __LITTLE_ENDIAN /* __FLOAT_WORD_ORER == __BIG_ENDIAN */ { unsigned char *ci, *co; ci = (unsigned char *)&dVal; co = (unsigned char *)output; co[0] = ci[3]; co[1] = ci[2]; co[2] = ci[1]; co[3] = ci[0]; co[4] = ci[7]; co[5] = ci[6]; co[6] = ci[5]; co[7] = ci[4]; } #else /* __BYTE_ORDER == __BIG_ENDIAN && __FLOAT_WORD_ORER == __LITTLE_ENDIAN */ { unsigned char *ci, *co; ci = (unsigned char *)&dVal; co = (unsigned char *)output; co[0] = ci[4]; co[1] = ci[5]; co[2] = ci[6]; co[3] = ci[7]; co[4] = ci[0]; co[5] = ci[1]; co[6] = ci[2]; co[7] = ci[3]; } #endif #endif return output+8; } char * AMF_EncodeBoolean(char *output, char *outend, int bVal) { if (output+2 > outend) return NULL; *output++ = AMF_BOOLEAN; *output++ = bVal ? 0x01 : 0x00; return output; } char * AMF_EncodeNamedString(char *output, char *outend, const AVal *strName, const AVal *strValue) { if (output+2+strName->av_len > outend) return NULL; output = AMF_EncodeInt16(output, outend, strName->av_len); memcpy(output, strName->av_val, strName->av_len); output += strName->av_len; return AMF_EncodeString(output, outend, strValue); } char * AMF_EncodeNamedNumber(char *output, char *outend, const AVal *strName, double dVal) { if (output+2+strName->av_len > outend) return NULL; output = AMF_EncodeInt16(output, outend, strName->av_len); memcpy(output, strName->av_val, strName->av_len); output += strName->av_len; return AMF_EncodeNumber(output, outend, dVal); } char * AMF_EncodeNamedBoolean(char *output, char *outend, const AVal *strName, int bVal) { if (output+2+strName->av_len > outend) return NULL; output = AMF_EncodeInt16(output, outend, strName->av_len); memcpy(output, strName->av_val, strName->av_len); output += strName->av_len; return AMF_EncodeBoolean(output, outend, bVal); } void AMFProp_GetName(AMFObjectProperty *prop, AVal *name) { *name = prop->p_name; } void AMFProp_SetName(AMFObjectProperty *prop, AVal *name) { prop->p_name = *name; } AMFDataType AMFProp_GetType(AMFObjectProperty *prop) { return prop->p_type; } double AMFProp_GetNumber(AMFObjectProperty *prop) { return prop->p_vu.p_number; } int AMFProp_GetBoolean(AMFObjectProperty *prop) { return prop->p_vu.p_number != 0; } void AMFProp_GetString(AMFObjectProperty *prop, AVal *str) { *str = prop->p_vu.p_aval; } void AMFProp_GetObject(AMFObjectProperty *prop, AMFObject *obj) { *obj = prop->p_vu.p_object; } int AMFProp_IsValid(AMFObjectProperty *prop) { return prop->p_type != AMF_INVALID; } char * AMFProp_Encode(AMFObjectProperty *prop, char *pBuffer, char *pBufEnd) { if (prop->p_type == AMF_INVALID) return NULL; if (prop->p_type != AMF_NULL && pBuffer + prop->p_name.av_len + 2 + 1 >= pBufEnd) return NULL; if (prop->p_type != AMF_NULL && prop->p_name.av_len) { *pBuffer++ = prop->p_name.av_len >> 8; *pBuffer++ = prop->p_name.av_len & 0xff; memcpy(pBuffer, prop->p_name.av_val, prop->p_name.av_len); pBuffer += prop->p_name.av_len; } switch (prop->p_type) { case AMF_NUMBER: pBuffer = AMF_EncodeNumber(pBuffer, pBufEnd, prop->p_vu.p_number); break; case AMF_BOOLEAN: pBuffer = AMF_EncodeBoolean(pBuffer, pBufEnd, prop->p_vu.p_number != 0); break; case AMF_STRING: pBuffer = AMF_EncodeString(pBuffer, pBufEnd, &prop->p_vu.p_aval); break; case AMF_NULL: if (pBuffer+1 >= pBufEnd) return NULL; *pBuffer++ = AMF_NULL; break; case AMF_OBJECT: pBuffer = AMF_Encode(&prop->p_vu.p_object, pBuffer, pBufEnd); break; case AMF_ECMA_ARRAY: pBuffer = AMF_EncodeEcmaArray(&prop->p_vu.p_object, pBuffer, pBufEnd); break; case AMF_STRICT_ARRAY: pBuffer = AMF_EncodeArray(&prop->p_vu.p_object, pBuffer, pBufEnd); break; default: RTMP_Log(RTMP_LOGERROR, "%s, invalid type. %d", __FUNCTION__, prop->p_type); pBuffer = NULL; }; return pBuffer; } #define AMF3_INTEGER_MAX 268435455 #define AMF3_INTEGER_MIN -268435456 int AMF3ReadInteger(const char *data, int32_t *valp) { int i = 0; int32_t val = 0; while (i <= 2) { /* handle first 3 bytes */ if (data[i] & 0x80) { /* byte used */ val <<= 7; /* shift up */ val |= (data[i] & 0x7f); /* add bits */ i++; } else { break; } } if (i > 2) { /* use 4th byte, all 8bits */ val <<= 8; val |= data[3]; /* range check */ if (val > AMF3_INTEGER_MAX) val -= (1 << 29); } else { /* use 7bits of last unparsed byte (0xxxxxxx) */ val <<= 7; val |= data[i]; } *valp = val; return i > 2 ? 4 : i + 1; } int AMF3ReadString(const char *data, AVal *str) { int32_t ref = 0; int len; assert(str != 0); len = AMF3ReadInteger(data, &ref); data += len; if ((ref & 0x1) == 0) { /* reference: 0xxx */ uint32_t refIndex = (ref >> 1); RTMP_Log(RTMP_LOGDEBUG, "%s, string reference, index: %d, not supported, ignoring!", __FUNCTION__, refIndex); return len; } else { uint32_t nSize = (ref >> 1); str->av_val = (char *)data; str->av_len = nSize; return len + nSize; } return len; } int AMF3Prop_Decode(AMFObjectProperty *prop, const char *pBuffer, int nSize, int bDecodeName) { int nOriginalSize = nSize; AMF3DataType type; prop->p_name.av_len = 0; prop->p_name.av_val = NULL; if (nSize == 0 || !pBuffer) { RTMP_Log(RTMP_LOGDEBUG, "empty buffer/no buffer pointer!"); return -1; } /* decode name */ if (bDecodeName) { AVal name; int nRes = AMF3ReadString(pBuffer, &name); if (name.av_len <= 0) return nRes; prop->p_name = name; pBuffer += nRes; nSize -= nRes; } /* decode */ type = *pBuffer++; nSize--; switch (type) { case AMF3_UNDEFINED: case AMF3_NULL: prop->p_type = AMF_NULL; break; case AMF3_FALSE: prop->p_type = AMF_BOOLEAN; prop->p_vu.p_number = 0.0; break; case AMF3_TRUE: prop->p_type = AMF_BOOLEAN; prop->p_vu.p_number = 1.0; break; case AMF3_INTEGER: { int32_t res = 0; int len = AMF3ReadInteger(pBuffer, &res); prop->p_vu.p_number = (double)res; prop->p_type = AMF_NUMBER; nSize -= len; break; } case AMF3_DOUBLE: if (nSize < 8) return -1; prop->p_vu.p_number = AMF_DecodeNumber(pBuffer); prop->p_type = AMF_NUMBER; nSize -= 8; break; case AMF3_STRING: case AMF3_XML_DOC: case AMF3_XML: { int len = AMF3ReadString(pBuffer, &prop->p_vu.p_aval); prop->p_type = AMF_STRING; nSize -= len; break; } case AMF3_DATE: { int32_t res = 0; int len = AMF3ReadInteger(pBuffer, &res); nSize -= len; pBuffer += len; if ((res & 0x1) == 0) { /* reference */ uint32_t nIndex = (res >> 1); RTMP_Log(RTMP_LOGDEBUG, "AMF3_DATE reference: %d, not supported!", nIndex); } else { if (nSize < 8) return -1; prop->p_vu.p_number = AMF_DecodeNumber(pBuffer); nSize -= 8; prop->p_type = AMF_NUMBER; } break; } case AMF3_OBJECT: { int nRes = AMF3_Decode(&prop->p_vu.p_object, pBuffer, nSize, TRUE); if (nRes == -1) return -1; nSize -= nRes; prop->p_type = AMF_OBJECT; break; } case AMF3_ARRAY: case AMF3_BYTE_ARRAY: default: RTMP_Log(RTMP_LOGDEBUG, "%s - AMF3 unknown/unsupported datatype 0x%02x, @%p", __FUNCTION__, (unsigned char)(*pBuffer), pBuffer); return -1; } return nOriginalSize - nSize; } int AMFProp_Decode(AMFObjectProperty *prop, const char *pBuffer, int nSize, int bDecodeName) { int nOriginalSize = nSize; int nRes; prop->p_name.av_len = 0; prop->p_name.av_val = NULL; if (nSize == 0 || !pBuffer) { RTMP_Log(RTMP_LOGDEBUG, "%s: Empty buffer/no buffer pointer!", __FUNCTION__); return -1; } if (bDecodeName && nSize < 4) { /* at least name (length + at least 1 byte) and 1 byte of data */ RTMP_Log(RTMP_LOGDEBUG, "%s: Not enough data for decoding with name, less than 4 bytes!", __FUNCTION__); return -1; } if (bDecodeName) { unsigned short nNameSize = AMF_DecodeInt16(pBuffer); if (nNameSize > nSize - 2) { RTMP_Log(RTMP_LOGDEBUG, "%s: Name size out of range: namesize (%d) > len (%d) - 2", __FUNCTION__, nNameSize, nSize); return -1; } AMF_DecodeString(pBuffer, &prop->p_name); nSize -= 2 + nNameSize; pBuffer += 2 + nNameSize; } if (nSize == 0) { return -1; } nSize--; prop->p_type = *pBuffer++; switch (prop->p_type) { case AMF_NUMBER: if (nSize < 8) return -1; prop->p_vu.p_number = AMF_DecodeNumber(pBuffer); nSize -= 8; break; case AMF_BOOLEAN: if (nSize < 1) return -1; prop->p_vu.p_number = (double)AMF_DecodeBoolean(pBuffer); nSize--; break; case AMF_STRING: { unsigned short nStringSize = AMF_DecodeInt16(pBuffer); if (nSize < (long)nStringSize + 2) return -1; AMF_DecodeString(pBuffer, &prop->p_vu.p_aval); nSize -= (2 + nStringSize); break; } case AMF_OBJECT: { int nRes = AMF_Decode(&prop->p_vu.p_object, pBuffer, nSize, TRUE); if (nRes == -1) return -1; nSize -= nRes; break; } case AMF_MOVIECLIP: { RTMP_Log(RTMP_LOGERROR, "AMF_MOVIECLIP reserved!"); return -1; break; } case AMF_NULL: case AMF_UNDEFINED: case AMF_UNSUPPORTED: prop->p_type = AMF_NULL; break; case AMF_REFERENCE: { RTMP_Log(RTMP_LOGERROR, "AMF_REFERENCE not supported!"); return -1; break; } case AMF_ECMA_ARRAY: { nSize -= 4; /* next comes the rest, mixed array has a final 0x000009 mark and names, so its an object */ nRes = AMF_Decode(&prop->p_vu.p_object, pBuffer + 4, nSize, TRUE); if (nRes == -1) return -1; nSize -= nRes; break; } case AMF_OBJECT_END: { return -1; break; } case AMF_STRICT_ARRAY: { unsigned int nArrayLen = AMF_DecodeInt32(pBuffer); nSize -= 4; nRes = AMF_DecodeArray(&prop->p_vu.p_object, pBuffer + 4, nSize, nArrayLen, FALSE); if (nRes == -1) return -1; nSize -= nRes; break; } case AMF_DATE: { RTMP_Log(RTMP_LOGDEBUG, "AMF_DATE"); if (nSize < 10) return -1; prop->p_vu.p_number = AMF_DecodeNumber(pBuffer); prop->p_UTCoffset = AMF_DecodeInt16(pBuffer + 8); nSize -= 10; break; } case AMF_LONG_STRING: case AMF_XML_DOC: { unsigned int nStringSize = AMF_DecodeInt32(pBuffer); if (nSize < (long)nStringSize + 4) return -1; AMF_DecodeLongString(pBuffer, &prop->p_vu.p_aval); nSize -= (4 + nStringSize); if (prop->p_type == AMF_LONG_STRING) prop->p_type = AMF_STRING; break; } case AMF_RECORDSET: { RTMP_Log(RTMP_LOGERROR, "AMF_RECORDSET reserved!"); return -1; break; } case AMF_TYPED_OBJECT: { RTMP_Log(RTMP_LOGERROR, "AMF_TYPED_OBJECT not supported!"); return -1; break; } case AMF_AVMPLUS: { int nRes = AMF3_Decode(&prop->p_vu.p_object, pBuffer, nSize, TRUE); if (nRes == -1) return -1; nSize -= nRes; prop->p_type = AMF_OBJECT; break; } default: RTMP_Log(RTMP_LOGDEBUG, "%s - unknown datatype 0x%02x, @%p", __FUNCTION__, prop->p_type, pBuffer - 1); return -1; } return nOriginalSize - nSize; } void AMFProp_Dump(AMFObjectProperty *prop) { char strRes[256]; char str[256]; AVal name; if (prop->p_type == AMF_INVALID) { RTMP_Log(RTMP_LOGDEBUG, "Property: INVALID"); return; } if (prop->p_type == AMF_NULL) { RTMP_Log(RTMP_LOGDEBUG, "Property: NULL"); return; } if (prop->p_name.av_len) { name = prop->p_name; } else { name.av_val = "no-name."; name.av_len = sizeof("no-name.") - 1; } if (name.av_len > 18) name.av_len = 18; snprintf(strRes, 255, "Name: %18.*s, ", name.av_len, name.av_val); if (prop->p_type == AMF_OBJECT) { RTMP_Log(RTMP_LOGDEBUG, "Property: <%sOBJECT>", strRes); AMF_Dump(&prop->p_vu.p_object); return; } else if (prop->p_type == AMF_ECMA_ARRAY) { RTMP_Log(RTMP_LOGDEBUG, "Property: <%sECMA_ARRAY>", strRes); AMF_Dump(&prop->p_vu.p_object); return; } else if (prop->p_type == AMF_STRICT_ARRAY) { RTMP_Log(RTMP_LOGDEBUG, "Property: <%sSTRICT_ARRAY>", strRes); AMF_Dump(&prop->p_vu.p_object); return; } switch (prop->p_type) { case AMF_NUMBER: snprintf(str, 255, "NUMBER:\t%.2f", prop->p_vu.p_number); break; case AMF_BOOLEAN: snprintf(str, 255, "BOOLEAN:\t%s", prop->p_vu.p_number != 0.0 ? "TRUE" : "FALSE"); break; case AMF_STRING: snprintf(str, 255, "STRING:\t%.*s", prop->p_vu.p_aval.av_len, prop->p_vu.p_aval.av_val); break; case AMF_DATE: snprintf(str, 255, "DATE:\ttimestamp: %.2f, UTC offset: %d", prop->p_vu.p_number, prop->p_UTCoffset); break; default: snprintf(str, 255, "INVALID TYPE 0x%02x", (unsigned char)prop->p_type); } RTMP_Log(RTMP_LOGDEBUG, "Property: <%s%s>", strRes, str); } void AMFProp_Reset(AMFObjectProperty *prop) { if (prop->p_type == AMF_OBJECT) AMF_Reset(&prop->p_vu.p_object); else { prop->p_vu.p_aval.av_len = 0; prop->p_vu.p_aval.av_val = NULL; } prop->p_type = AMF_INVALID; } /* AMFObject */ char * AMF_Encode(AMFObject *obj, char *pBuffer, char *pBufEnd) { int i; if (pBuffer+4 >= pBufEnd) return NULL; *pBuffer++ = AMF_OBJECT; for (i = 0; i < obj->o_num; i++) { char *res = AMFProp_Encode(&obj->o_props[i], pBuffer, pBufEnd); if (res == NULL) { RTMP_Log(RTMP_LOGERROR, "AMF_Encode - failed to encode property in index %d", i); break; } else { pBuffer = res; } } if (pBuffer + 3 >= pBufEnd) return NULL; /* no room for the end marker */ pBuffer = AMF_EncodeInt24(pBuffer, pBufEnd, AMF_OBJECT_END); return pBuffer; } char * AMF_EncodeEcmaArray(AMFObject *obj, char *pBuffer, char *pBufEnd) { int i; if (pBuffer+4 >= pBufEnd) return NULL; *pBuffer++ = AMF_ECMA_ARRAY; pBuffer = AMF_EncodeInt32(pBuffer, pBufEnd, obj->o_num); for (i = 0; i < obj->o_num; i++) { char *res = AMFProp_Encode(&obj->o_props[i], pBuffer, pBufEnd); if (res == NULL) { RTMP_Log(RTMP_LOGERROR, "AMF_Encode - failed to encode property in index %d", i); break; } else { pBuffer = res; } } if (pBuffer + 3 >= pBufEnd) return NULL; /* no room for the end marker */ pBuffer = AMF_EncodeInt24(pBuffer, pBufEnd, AMF_OBJECT_END); return pBuffer; } char * AMF_EncodeArray(AMFObject *obj, char *pBuffer, char *pBufEnd) { int i; if (pBuffer+4 >= pBufEnd) return NULL; *pBuffer++ = AMF_STRICT_ARRAY; pBuffer = AMF_EncodeInt32(pBuffer, pBufEnd, obj->o_num); for (i = 0; i < obj->o_num; i++) { char *res = AMFProp_Encode(&obj->o_props[i], pBuffer, pBufEnd); if (res == NULL) { RTMP_Log(RTMP_LOGERROR, "AMF_Encode - failed to encode property in index %d", i); break; } else { pBuffer = res; } } //if (pBuffer + 3 >= pBufEnd) // return NULL; /* no room for the end marker */ //pBuffer = AMF_EncodeInt24(pBuffer, pBufEnd, AMF_OBJECT_END); return pBuffer; } int AMF_DecodeArray(AMFObject *obj, const char *pBuffer, int nSize, int nArrayLen, int bDecodeName) { int nOriginalSize = nSize; int bError = FALSE; obj->o_num = 0; obj->o_props = NULL; while (nArrayLen > 0) { AMFObjectProperty prop; int nRes; nArrayLen--; nRes = AMFProp_Decode(&prop, pBuffer, nSize, bDecodeName); if (nRes == -1) bError = TRUE; else { nSize -= nRes; pBuffer += nRes; AMF_AddProp(obj, &prop); } } if (bError) return -1; return nOriginalSize - nSize; } int AMF3_Decode(AMFObject *obj, const char *pBuffer, int nSize, int bAMFData) { int nOriginalSize = nSize; int32_t ref; int len; obj->o_num = 0; obj->o_props = NULL; if (bAMFData) { if (*pBuffer != AMF3_OBJECT) RTMP_Log(RTMP_LOGERROR, "AMF3 Object encapsulated in AMF stream does not start with AMF3_OBJECT!"); pBuffer++; nSize--; } ref = 0; len = AMF3ReadInteger(pBuffer, &ref); pBuffer += len; nSize -= len; if ((ref & 1) == 0) { /* object reference, 0xxx */ uint32_t objectIndex = (ref >> 1); RTMP_Log(RTMP_LOGDEBUG, "Object reference, index: %d", objectIndex); } else /* object instance */ { int32_t classRef = (ref >> 1); AMF3ClassDef cd = { {0, 0} }; AMFObjectProperty prop; if ((classRef & 0x1) == 0) { /* class reference */ uint32_t classIndex = (classRef >> 1); RTMP_Log(RTMP_LOGDEBUG, "Class reference: %d", classIndex); } else { int32_t classExtRef = (classRef >> 1); int i; cd.cd_externalizable = (classExtRef & 0x1) == 1; cd.cd_dynamic = ((classExtRef >> 1) & 0x1) == 1; cd.cd_num = classExtRef >> 2; /* class name */ len = AMF3ReadString(pBuffer, &cd.cd_name); nSize -= len; pBuffer += len; /*std::string str = className; */ RTMP_Log(RTMP_LOGDEBUG, "Class name: %s, externalizable: %d, dynamic: %d, classMembers: %d", cd.cd_name.av_val, cd.cd_externalizable, cd.cd_dynamic, cd.cd_num); for (i = 0; i < cd.cd_num; i++) { AVal memberName; len = AMF3ReadString(pBuffer, &memberName); RTMP_Log(RTMP_LOGDEBUG, "Member: %s", memberName.av_val); AMF3CD_AddProp(&cd, &memberName); nSize -= len; pBuffer += len; } } /* add as referencable object */ if (cd.cd_externalizable) { int nRes; AVal name = AVC("DEFAULT_ATTRIBUTE"); RTMP_Log(RTMP_LOGDEBUG, "Externalizable, TODO check"); nRes = AMF3Prop_Decode(&prop, pBuffer, nSize, FALSE); if (nRes == -1) RTMP_Log(RTMP_LOGDEBUG, "%s, failed to decode AMF3 property!", __FUNCTION__); else { nSize -= nRes; pBuffer += nRes; } AMFProp_SetName(&prop, &name); AMF_AddProp(obj, &prop); } else { int nRes, i; for (i = 0; i < cd.cd_num; i++) /* non-dynamic */ { nRes = AMF3Prop_Decode(&prop, pBuffer, nSize, FALSE); if (nRes == -1) RTMP_Log(RTMP_LOGDEBUG, "%s, failed to decode AMF3 property!", __FUNCTION__); AMFProp_SetName(&prop, AMF3CD_GetProp(&cd, i)); AMF_AddProp(obj, &prop); pBuffer += nRes; nSize -= nRes; } if (cd.cd_dynamic) { int len = 0; do { nRes = AMF3Prop_Decode(&prop, pBuffer, nSize, TRUE); AMF_AddProp(obj, &prop); pBuffer += nRes; nSize -= nRes; len = prop.p_name.av_len; } while (len > 0); } } RTMP_Log(RTMP_LOGDEBUG, "class object!"); } return nOriginalSize - nSize; } int AMF_Decode(AMFObject *obj, const char *pBuffer, int nSize, int bDecodeName) { int nOriginalSize = nSize; int bError = FALSE; /* if there is an error while decoding - try to at least find the end mark AMF_OBJECT_END */ obj->o_num = 0; obj->o_props = NULL; while (nSize > 0) { AMFObjectProperty prop; int nRes; if (nSize >=3 && AMF_DecodeInt24(pBuffer) == AMF_OBJECT_END) { nSize -= 3; bError = FALSE; break; } if (bError) { RTMP_Log(RTMP_LOGERROR, "DECODING ERROR, IGNORING BYTES UNTIL NEXT KNOWN PATTERN!"); nSize--; pBuffer++; continue; } nRes = AMFProp_Decode(&prop, pBuffer, nSize, bDecodeName); if (nRes == -1) bError = TRUE; else { nSize -= nRes; pBuffer += nRes; AMF_AddProp(obj, &prop); } } if (bError) return -1; return nOriginalSize - nSize; } void AMF_AddProp(AMFObject *obj, const AMFObjectProperty *prop) { if (!(obj->o_num & 0x0f)) obj->o_props = realloc(obj->o_props, (obj->o_num + 16) * sizeof(AMFObjectProperty)); memcpy(&obj->o_props[obj->o_num++], prop, sizeof(AMFObjectProperty)); } int AMF_CountProp(AMFObject *obj) { return obj->o_num; } AMFObjectProperty * AMF_GetProp(AMFObject *obj, const AVal *name, int nIndex) { if (nIndex >= 0) { if (nIndex < obj->o_num) return &obj->o_props[nIndex]; } else { int n; for (n = 0; n < obj->o_num; n++) { if (AVMATCH(&obj->o_props[n].p_name, name)) return &obj->o_props[n]; } } return (AMFObjectProperty *)&AMFProp_Invalid; } void AMF_Dump(AMFObject *obj) { int n; RTMP_Log(RTMP_LOGDEBUG, "(object begin)"); for (n = 0; n < obj->o_num; n++) { AMFProp_Dump(&obj->o_props[n]); } RTMP_Log(RTMP_LOGDEBUG, "(object end)"); } void AMF_Reset(AMFObject *obj) { int n; for (n = 0; n < obj->o_num; n++) { AMFProp_Reset(&obj->o_props[n]); } free(obj->o_props); obj->o_props = NULL; obj->o_num = 0; } /* AMF3ClassDefinition */ void AMF3CD_AddProp(AMF3ClassDef *cd, AVal *prop) { if (!(cd->cd_num & 0x0f)) cd->cd_props = realloc(cd->cd_props, (cd->cd_num + 16) * sizeof(AVal)); cd->cd_props[cd->cd_num++] = *prop; } AVal * AMF3CD_GetProp(AMF3ClassDef *cd, int nIndex) { if (nIndex >= cd->cd_num) return (AVal *)&AV_empty; return &cd->cd_props[nIndex]; } python-librtmp-0.3.0/src/librtmp/log.h0000644000175000017500000000425512224632445021004 0ustar chrippachrippa00000000000000/* * Copyright (C) 2008-2009 Andrej Stepanchuk * Copyright (C) 2009-2010 Howard Chu * * This file is part of librtmp. * * librtmp is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1, * or (at your option) any later version. * * librtmp is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with librtmp see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/lgpl.html */ #ifndef __RTMP_LOG_H__ #define __RTMP_LOG_H__ #include #include #include #ifdef __cplusplus extern "C" { #endif /* Enable this to get full debugging output */ /* #define _DEBUG */ #ifdef _DEBUG #undef NODEBUG #endif typedef enum { RTMP_LOGCRIT=0, RTMP_LOGERROR, RTMP_LOGWARNING, RTMP_LOGINFO, RTMP_LOGDEBUG, RTMP_LOGDEBUG2, RTMP_LOGALL } RTMP_LogLevel; extern RTMP_LogLevel RTMP_debuglevel; typedef void (RTMP_LogCallback)(int level, const char *fmt, va_list); void RTMP_LogSetCallback(RTMP_LogCallback *cb); void RTMP_LogSetOutput(FILE *file); #ifdef __GNUC__ void RTMP_LogPrintf(const char *format, ...) __attribute__ ((__format__ (__printf__, 1, 2))); void RTMP_LogStatus(const char *format, ...) __attribute__ ((__format__ (__printf__, 1, 2))); void RTMP_Log(int level, const char *format, ...) __attribute__ ((__format__ (__printf__, 2, 3))); #else void RTMP_LogPrintf(const char *format, ...); void RTMP_LogStatus(const char *format, ...); void RTMP_Log(int level, const char *format, ...); #endif void RTMP_LogHex(int level, const uint8_t *data, unsigned long len); void RTMP_LogHexString(int level, const uint8_t *data, unsigned long len); void RTMP_LogSetLevel(RTMP_LogLevel lvl); RTMP_LogLevel RTMP_LogGetLevel(void); #ifdef __cplusplus } #endif #endif python-librtmp-0.3.0/src/librtmp/bytes.h0000644000175000017500000000475312224632456021356 0ustar chrippachrippa00000000000000/* * Copyright (C) 2005-2008 Team XBMC * http://www.xbmc.org * Copyright (C) 2008-2009 Andrej Stepanchuk * Copyright (C) 2009-2010 Howard Chu * * This file is part of librtmp. * * librtmp is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1, * or (at your option) any later version. * * librtmp is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with librtmp see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/lgpl.html */ #ifndef __BYTES_H__ #define __BYTES_H__ #include #ifdef _WIN32 /* Windows is little endian only */ #define __LITTLE_ENDIAN 1234 #define __BIG_ENDIAN 4321 #define __BYTE_ORDER __LITTLE_ENDIAN #define __FLOAT_WORD_ORDER __BYTE_ORDER typedef unsigned char uint8_t; #else /* !_WIN32 */ #include #if defined(BYTE_ORDER) && !defined(__BYTE_ORDER) #define __BYTE_ORDER BYTE_ORDER #endif #if defined(BIG_ENDIAN) && !defined(__BIG_ENDIAN) #define __BIG_ENDIAN BIG_ENDIAN #endif #if defined(LITTLE_ENDIAN) && !defined(__LITTLE_ENDIAN) #define __LITTLE_ENDIAN LITTLE_ENDIAN #endif #endif /* !_WIN32 */ /* define default endianness */ #ifndef __LITTLE_ENDIAN #define __LITTLE_ENDIAN 1234 #endif #ifndef __BIG_ENDIAN #define __BIG_ENDIAN 4321 #endif #ifndef __BYTE_ORDER #warning "Byte order not defined on your system, assuming little endian!" #define __BYTE_ORDER __LITTLE_ENDIAN #endif /* ok, we assume to have the same float word order and byte order if float word order is not defined */ #ifndef __FLOAT_WORD_ORDER #warning "Float word order not defined, assuming the same as byte order!" #define __FLOAT_WORD_ORDER __BYTE_ORDER #endif #if !defined(__BYTE_ORDER) || !defined(__FLOAT_WORD_ORDER) #error "Undefined byte or float word order!" #endif #if __FLOAT_WORD_ORDER != __BIG_ENDIAN && __FLOAT_WORD_ORDER != __LITTLE_ENDIAN #error "Unknown/unsupported float word order!" #endif #if __BYTE_ORDER != __BIG_ENDIAN && __BYTE_ORDER != __LITTLE_ENDIAN #error "Unknown/unsupported byte order!" #endif #endif python-librtmp-0.3.0/MANIFEST.in0000644000175000017500000000025712530702453017343 0ustar chrippachrippa00000000000000include AUTHORS.rst include CONTRIBUTING.rst include HISTORY.rst include LICENSE include README.rst include tox.ini recursive-include tests *.py recursive-include src *.c *.h python-librtmp-0.3.0/AUTHORS.rst0000644000175000017500000000024212216371403017454 0ustar chrippachrippa00000000000000======= Credits ======= Development Lead ---------------- * Christopher Rosell Contributors ------------ None yet. Why not be the first? python-librtmp-0.3.0/HISTORY.rst0000644000175000017500000000204612530705154017477 0ustar chrippachrippa00000000000000.. :changelog: History ------- 0.3.0 (2015-05-25) ^^^^^^^^^^^^^^^^^^ * Added update_buffer option (enabled by default) to RTMP.create_stream, which enables a hack to increase throughput. * Added a update_buffer method to RTMPStream. * We now require at least version 1.0.1 of cffi. 0.2.2 (2015-04-15) ^^^^^^^^^^^^^^^^^^ * Fixed proxy not being used by librtmp. * Added support for Cygwin, patch by @schrobby. (#17) 0.2.1 (2014-09-01) ^^^^^^^^^^^^^^^^^^ * Fixed expected bytes type on Python 2. * Fixed singledispatch dependency condition. 0.2.0 (2014-04-07) ^^^^^^^^^^^^^^^^^^ * RTMPError now inherits from IOError. * Fixed MSVC build. * Added librtmp.so.1 to library paths, patch by Athanasios Oikonomou. (#4) * Added librtmp.dylib to library paths, patch by Will Donohoe. (#6) 0.1.2 (2013-10-08) ^^^^^^^^^^^^^^^^^^ * Fixed compilation issue on some platforms. * Fixed AMF issue on older librtmp versions. (#1) 0.1.1 (2013-09-25) ^^^^^^^^^^^^^^^^^^ * Fixed packaging issues. 0.1.0 (2013-09-23) ^^^^^^^^^^^^^^^^^^ * First release on PyPI.