PDA

View Full Version : librtmp and python


Gontran
03-18-2012, 08:33 AM
Hi everybody,


since a few days I'm facing a very stressful issue with librtmp and python 2.7 (my os is Windows 7).

I'm trying to connect to a stream on justin tv but I've probably an encoding issue with jtv token string.

Here is my code:

print 'instantiate rtmp connection'
r = dll.RTMP_Alloc()
dll.RTMP_Init(r)
dll.RTMP_SetupURL(r, c_char_p(tcUrl))

print 'connect to stream'
dll.RTMP_Connect(r, None)

print 'read stream'
dll.RTMP_Read(r)


tcUrl looks like this:
tcUrl=rtmp://199.9.255.143/app/jtv_toTs9DzRvV8WPazW swfUrl=http://www-cdn.jtvnw.net/widgets/live_site_player.ra3170e7f148ff63b2f1b4dfef2840842 de54e7f8
.swf token=a6faa3f74e0927e80039bb612522d637530d6395:{\"swfDomains\": [\"justin.tv\", \"jtvx.com\", \"xarth.com\", \"twitchtv.com\", \"twitch.tv\", \"n
ewjtv.com\", \"jtvnw.net\", \"wdtinc.com\", \"imapweather.com\", \"facebook.com\", \"starcrafting.com\"], \"streamName\": \"jtv_toTs9DzRvV8WPazW\", \"
expiration\": 1332084704.643841, \"geo_ip\": \"82.243.172.91\", \"server\": \"lhr01-video3-2\"}

does anybody knows how to fix this?

Thanks

Regards,

Gontran

KSV
03-18-2012, 10:53 AM
since a few days I'm facing a very stressful issue with librtmp and python 2.7 (my os is Windows 7).

I'm trying to connect to a stream on justin tv but I've probably an encoding issue with jtv token string.


You could have avoided that if you cared to read librtmp manpage. it clearly specifies
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.

their are two problems with your code. first correct option is jtv not token which is for SecureToken. second escaping " as \" is only required on shell or cmd prompt. correctly encoded token may look like this.
jtv=a6faa3f74e0927e80039bb612522d637530d6395:{\22\ 20swfDomains\22:\20[\22justin.tv\22,\20\22jtvx.com\22,\20\22xarth.com\ 22,\20\22twitchtv.com\22,\20\22twitch.tv\22,\20\22 newjtv.com\22,\20\22jtvnw.net\22,\20\22wdtinc.com\ 22,\20\22imapweather.com\22,\20\22facebook.com\22, \20\22starcrafting.com\22],\20\22streamName\22:\20\22jtv_toTs9DzRvV8WPazW\22 ,\20\22expiration\22:\201332084704.643841,\20\22ge o_ip\22:\20\2282.243.172.91\22,\20\22server\22:\20 \22lhr01-video3-2\22}

Gontran
03-18-2012, 04:15 PM
Thank you KSV, you made my day.
Though I read the doc, I dont understand how I missed that!

Gontran
03-19-2012, 07:15 PM
Now that I managed to retrieve a stream, I would like to get log from librtmp.
According to ctypes documentation and given functions signatures from log.h/log.c, I wrote this in my code:


CMPFUNC = CFUNCTYPE(c_int, c_int, c_char_p, c_void_p)
def logCallBack(level, message, va_list):
dll.RTMP_LogPrintf(message, va_list)
return 0

mLogCallBack = CMPFUNC(logCallBack)
...
dll.RTMP_LogSetCallback(mLogCallBack)


but instead of printing a nice readable log, I got a dirty output:


Parsed host : ☺Parsed app : ☺connect to stream
ÄßGj♫¨', ... connected, handshaking?ßGj♥: Type Answer : 27DBEC?ßGjä?┼♣: Server Uptime : 2612204?ßGj♥: FMS Version : 2612204.2612100.488283845.4882
83530?ßGj♥: Handshaking finished....ÄßGj♫¨', handshaked│°Gj♀☻: fd=2614284, size=2614180Invoking ▒?'ä°Gj♀☻: fd=2618556`÷Gjá%&: server BW = 2618572ä°Gj♀
☻: fd=2618556o÷Gjá%&: client BW = 2618572 2618468ä°Gj♀☻: fd=2618556?÷Gj, received ctrl. type: 2618572, len: 2618468?÷Gj, Stream Begin 2618572ä°Gj♀☻: f
d=26185568÷Gj, received: chunk size change to 2618572ä°Gj♀☻: fd=2618556N÷Gj♣☺, received: invoke 2618572 bytes(object begin)Property: <|±'☺>Property: <
|±'☺>Property: <|±'OBJECT>(object begin)Property: <,´'☺>Property: <,´'☺>Property: <,´'☺>(object end)Property: <|±'OBJECT>(object begin)Property: <,´'☺
>Property: <,´'☺>Property: <,´'☺>Property: <,´'OBJECT>(object begin)Property: <▄ý'☺>(object end)Property: <,´'☺>Property: <,´'☺>(object end)(object en
d)~÷Gjmt;, server invoking <☺>~÷Gj, received result for method call <☺>│°Gj♀☻: fd=2616844, size=2616740sending ctrl. type: 0x27f264│°Gj♀☻: fd=2616828,
size=2616724│°Gj♀☻: fd=2616828, size=2616724Invoking ?‗'UsherToken: ú?B☻,☺│°Gj♀☻: fd=2617164, size=2617060Invoking )¶'ä°Gj♀☻: fd=2618556N÷Gj↔, receiv
ed: invoke 2618572 bytes(object begin)Property: <|±'☺>Property: <|±'☺>Property: NULLProperty: <|±'☺>(object end)~÷Gj?⌂;, server invoking <☺>~÷Gj8¶;, r
eceived result for method call <☺>¶÷Gj, seekTime=2617308, stopTime=2617204, sending play: â─↑?♥â╚*?e¶╔├U?ýâý►SVW?¨?w►?E*ëE??F♦┴Ó☻?┌??↨│°Gj♀☻ : fd=26171
64, size=2617060Invoking )¶'sending ctrl. type: 0x27f264│°Gj♀☻: fd=2616828, size=2616724ä°Gj♀☻: fd=2618556N÷Gj%, received: invoke 2618572 bytes(object
begin)Property: <|±'☺>Property: <|±'☺>Property: NULLProperty: <|±'☺>(object end)~÷Gj?⌂;, server invoking <☺>~÷Gj, received result id 2617308 without
matching requestä°Gj♀☻: fd=2618556?÷Gj, received ctrl. type: 2618572, len: 2618468?÷Gj☺, Stream Begin 2618572ä°Gj♀☻: fd=2618556N÷Gjö, received: invoke
2618572 bytes(object begin)Property: <|±'☺>Property: <|±'☺>Property: NULLProperty: <|±'OBJECT>(object begin)Property: <,´'☺>Property: <,´'☺>Property:
<,´'☺>Property: <,´'☺>(object end)(object end)~÷Gj?t;, server invoking <☺>~÷Gj┴t;, onStatus: ☺ä°Gj♀☻: fd=2618556N÷Gj?, received: invoke 2618572 bytes
(object begin)Property: <|±'☺>Property: <|±'☺>Property: NULLProperty: <|±'OBJECT>(object begin)Property: <,´'☺>Property: <,´'☺>Property: <,´'☺>Propert
y: <,´'☺>Property: <,´'☺>Property: <,´'☺>(object end)(object end)~÷Gj?t;, server invoking <☺>~÷Gj┴t;, onStatus: ☺read stream
ä°Gj♀☻: fd=2618348N÷Gj,, received: notify 2618364 bytes(object begin)Property: <(§'☺>Property: <(§'OBJECT>(object begin)Property: <?‗'☺>(object end)(o
bject end)ä°Gj♀☻: fd=2618348ä°Gj♀☻: fd=2618348N÷Gj?♥, received: notify 2618364 bytes(object begin)Property: <(§'☺>Property: <(§'OBJECT>(object begin)P
roperty: <?‗'☺>Property: <?‗'☺>Property: <?‗'☺>Property: <?‗'☺>Property: <?‗'☺>Property: <?‗'☺>Property: <?‗'☺>Property: <?‗'☺>Property: <?‗'☺>Propert
y: <?‗'☺>Property: <?‗'☺>Property: <?‗'OBJECT>(object begin)Property: <?*'OBJECT>(object begin)Property: <8?'☺>Property: <8?'☺>Property: <8?'OBJECT>(o
bject begin)Property: <??'OBJECT>(object begin)Property: <?Ú'☺>(object end)(object end)Property: <8?'☺>(object end)Property: <?*'OBJECT>(object begin)
Property: <8?'☺>Property: <8?'☺>Property: <8?'OBJECT>(object begin)Property: <??'OBJECT>(object begin)Property: <?Ú'☺>(object end)(object end)Property
: <8?'☺>Property: <8?'☺>Property: <8?'☺>Property: <8?'☺>(object end)(object end)Property: <?‗'OBJECT>(object begin)Property: <?*'☺>Property: <?*'☺>Pro
perty: <?*'☺>Property: <?*'☺>Property: <?*'☺>Property: <?*'☺>Property: <?*'OBJECT>(object begin)Property: <8?'☺>Property: <8?'☺>Property: <8?'☺>(objec
t end)(object end)(object end)(object end)Metadata: ☺ ,±' ☺ ,±' ☺ ,±' ☺
,±' ☺ ,±' ☺ ,±' ☺ ,±' ☺ ,±' ☺ ,±' ☺
,±' ☺ ,±'☺: ☺ î?' ☺ î?'☺: ☺ ý?' ☺
î?' ☺ î?' ☺ î?'☺: ☺ ý?' ☺ î?' ☺ î?' ☺
î?' ☺ î?'☺: ☺ ▄´' ☺ ▄´' ☺ ▄´' ☺
▄´' ☺ ▄´' ☺ ▄´'☺: ☺ î?' ☺ î?' ☺ î?'ä°
Gj♀☻: fd=2618348│°Gj♀☻: fd=2618172, size=2618068Invoking ı¸'


I also tried to print "message" in my logCallBack() function but it gave me strings without values:


%s, ... connected, handshaking
%s: Type Answer : %02X
%s: Server Uptime : %d
%s: FMS Version : %d.%d.%d.%d
%s: Handshaking finished....
%s, handshaked
%s: fd=%d, size=%d
Invoking %s
%s: fd=%d
%s: server BW = %d
%s: fd=%d
%s: client BW = %d %d
%s: fd=%d
%s, received ctrl. type: %d, len: %d
...


I've been trying for almost 5 hours, digging into the web looking for a solution unsuccessfully.
That 's why I'm asking for help.

Thanks in advance.

Regards,


Gontran

KSV
03-20-2012, 05:01 AM
I am not a python coder so i can't provide you a definite answer. but what you are referring as message in your code is actually format string. va_list contains the actual data to be filled in format string to create complete string. you can't pass va_list to another function. try to use PyArg_VaParse.

Gontran
03-20-2012, 08:11 AM
Thank you KSV for your concern but as far as I know, PyArg_VaParse has to be used in c programms.
I tried to reproduce the same code as shown in this thread (http://stream-recorder.com/forum/using-librtmp-vb-c-application-t11025.html? but this doesn't work.

Anyway, I'll dig deeper tonight.

Regards,

Gontran

Gontran
03-20-2012, 10:14 AM
Just don't pay attention to my last messages: I was tired and doing all wrong!

The only thing needed was to set the log level (by default nothing) in order to see logs in the standard output:

#That's all!!!!
dll.RTMP_LogSetLevel(c_int(6))

Anyway, after cleaning it, I will post my code in case of somebody is interressed.

Gontran
03-20-2012, 05:04 PM
As I said earlier, here a little class that wraps librtmp in order to get streams:


# -*- coding: utf-8 -*-
import os
from ctypes import cdll, c_char_p, c_int, create_string_buffer, sizeof

class RTMP:
"""
This class encapsulates librtmp.dll in order to connect to a RTMP server and get video stream.
RTMPDUMP website : http://rtmpdump.mplayerhq.hu
LIBRTMP manpage : http://rtmpdump.mplayerhq.hu/librtmp.3.html
"""

LogLevel = {
"RTMP_LOGCRIT" : c_int(0),
"RTMP_LOGERROR" : c_int(1),
"RTMP_LOGWARNING" : c_int(2),
"RTMP_LOGINFO" : c_int(3),
"RTMP_LOGDEBUG" : c_int(4),
"RTMP_LOGDEBUG2" : c_int(5),
"RTMP_LOGALL" : c_int(6)
}

def __init__(self, tcURL, output_file_path, log_level=LogLevel["RTMP_LOGINFO"], buffer_size=64 * 1024):
# Load library
if os.name == 'nt':
self.lib = cdll.LoadLibrary('lib/librtmp.dll')
elif os.name == 'posix':
# TODO: load library for posix os
pass

# Initialize variables
self.tcURL = tcURL
self.output_file_path = output_file_path
self.log_level = log_level
self.buffer_size = buffer_size

#************************************************* ***********************
# Wrap native library functions

def Alloc(self):
"""Return a pointer to a new RTMP object"""
return self.lib.RTMP_Alloc()

def Init(self, r_pointer):
"""Init the RTMP object"""
self.lib.RTMP_Init(r_pointer)
return None

def SetupURL(self, r_pointer, tcURL):
"""
Setup the rtmp url
The rtmp url format is of the form
rtmp[t][e|s]://hostname[:port][/app[/playpath]]
"""
self.lib.RTMP_SetupURL(r_pointer, c_char_p(tcURL))
return None

def Connect(self, r_pointer):
"""Established network connection"""
self.lib.RTMP_Connect(r_pointer, None)
return None

def ConnectStream(self, r_pointer):
"""Established RTMP session"""
self.lib.RTMP_ConnectStream(r_pointer, 0)
return None

def Read(self, r_pointer, buffer):
"""
Reads bytes from the stream an write its into the buffer.
Returns the number of bytes read
When it returns 0 bytes, the stream is complete and may be closed
"""
return self.lib.RTMP_Read(r_pointer, buffer, sizeof(buffer))

def Close(self, r_pointer):
""" Closes the connection """
self.lib.RTMP_Close(r_pointer)
return None

def Free(self, r_pointer):
""" Frees the session """
self.lib.RTMP_Free(r_pointer)
return None

def LogSetLevel(self, log_level):
""" Defines RTMP_LogLevel used by output """
self.lib.RTMP_LogSetLevel(log_level)
return None

#************************************************* ***********************

def getVideoStream(self):
"""
Get the video stream from the rtmp server
"""
# Try to open the output file
try:
output_file = open(self.output_file_path, 'wb')
except:
print "Cannot open output file.\n Please check the output_file_path\n. Aborting."
return None

# Instantiate buffer
buffer = create_string_buffer(self.buffer_size)

# Set LogLevel
self.LogSetLevel(self.log_level)

# Setup RTMP connection
r_pointer = self.Alloc()
self.Init(r_pointer)
self.SetupURL(r_pointer, self.tcURL)

# Connect and establish session
self.Connect(r_pointer)
self.ConnectStream(r_pointer)

# Read stream
try:
while True:
# While result > 0, write buffer bytes into output_file
result = self.Read(r_pointer, buffer)
output_file.write(buffer[:result])
if result == 0:
break
except:
# Handles exception in order to close session and file properly
print "An exception occured. Ending session."

# Ends session and closes output_file
self.Close(r_pointer)
self.Free(r_pointer)
output_file.close()



In order to use it, just instantiate a new RTMP object and call getVideoStream():

# construct the url before as mentionned in librtmp manpage
m_rtmp = RTMP(tcUrl, 'out_file.flv')
m_rtmp.getVideoStream()


Feel free to improve it (I know it's not perfect) and post comments.
Hope this will be usefull!

Regards,

Gontran

newtonelectron
03-20-2012, 06:24 PM
As I said earlier, here a little class that wraps librtmp in order to get streams:


# -*- coding: utf-8 -*-
import os
from ctypes import cdll, c_char_p, c_int, create_string_buffer, sizeof

class RTMP:
"""
This class encapsulates librtmp.dll in order to connect to a RTMP server and get video stream.
RTMPDUMP website : http://rtmpdump.mplayerhq.hu
LIBRTMP manpage : http://rtmpdump.mplayerhq.hu/librtmp.3.html
"""

LogLevel = {
"RTMP_LOGCRIT" : c_int(0),
"RTMP_LOGERROR" : c_int(1),
"RTMP_LOGWARNING" : c_int(2),
"RTMP_LOGINFO" : c_int(3),
"RTMP_LOGDEBUG" : c_int(4),
"RTMP_LOGDEBUG2" : c_int(5),
"RTMP_LOGALL" : c_int(6)
}

def __init__(self, tcURL, output_file_path, log_level=LogLevel["RTMP_LOGINFO"], buffer_size=64 * 1024):
# Load library
if os.name == 'nt':
self.lib = cdll.LoadLibrary('lib/librtmp.dll')
elif os.name == 'posix':
# TODO: load library for posix os
pass

# Initialize variables
self.tcURL = tcURL
self.output_file_path = output_file_path
self.log_level = log_level
self.buffer_size = buffer_size

#************************************************* ***********************
# Wrap native library functions

def Alloc(self):
"""Return a pointer to a new RTMP object"""
return self.lib.RTMP_Alloc()

def Init(self, r_pointer):
"""Init the RTMP object"""
self.lib.RTMP_Init(r_pointer)
return None

def SetupURL(self, r_pointer, tcURL):
"""
Setup the rtmp url
The rtmp url format is of the form
rtmp[t][e|s]://hostname[:port][/app[/playpath]]
"""
self.lib.RTMP_SetupURL(r_pointer, c_char_p(tcURL))
return None

def Connect(self, r_pointer):
"""Established network connection"""
self.lib.RTMP_Connect(r_pointer, None)
return None

def ConnectStream(self, r_pointer):
"""Established RTMP session"""
self.lib.RTMP_ConnectStream(r_pointer, 0)
return None

def Read(self, r_pointer, buffer):
"""
Reads bytes from the stream an write its into the buffer.
Returns the number of bytes read
When it returns 0 bytes, the stream is complete and may be closed
"""
return self.lib.RTMP_Read(r_pointer, buffer, sizeof(buffer))

def Close(self, r_pointer):
""" Closes the connection """
self.lib.RTMP_Close(r_pointer)
return None

def Free(self, r_pointer):
""" Frees the session """
self.lib.RTMP_Free(r_pointer)
return None

def LogSetLevel(self, log_level):
""" Defines RTMP_LogLevel used by output """
self.lib.RTMP_LogSetLevel(log_level)
return None

#************************************************* ***********************

def getVideoStream(self):
"""
Get the video stream from the rtmp server
"""
# Try to open the output file
try:
output_file = open(self.output_file_path, 'wb')
except:
print "Cannot open output file.\n Please check the output_file_path\n. Aborting."
return None

# Instantiate buffer
buffer = create_string_buffer(self.buffer_size)

# Set LogLevel
self.LogSetLevel(self.log_level)

# Setup RTMP connection
r_pointer = self.Alloc()
self.Init(r_pointer)
self.SetupURL(r_pointer, self.tcURL)

# Connect and establish session
self.Connect(r_pointer)
self.ConnectStream(r_pointer)

# Read stream
try:
while True:
# While result > 0, write buffer bytes into output_file
result = self.Read(r_pointer, buffer)
output_file.write(buffer[:result])
if result == 0:
break
except:
# Handles exception in order to close session and file properly
print "An exception occured. Ending session."

# Ends session and closes output_file
self.Close(r_pointer)
self.Free(r_pointer)
output_file.close()



In order to use it, just instantiate a new RTMP object and call getVideoStream():

# construct the url before as mentionned in librtmp manpage
m_rtmp = RTMP(tcUrl, 'out_file.flv')
m_rtmp.getVideoStream()


Feel free to improve it (I know it's not perfect) and post comments.
Hope this will be usefull!

Regards,

Gontran
This script will fail misserably on linux in python 2 or 3.

I started work on something similar about a month ago and I'm surpirsed your script works.
I had to define custom ctype structure & union classes for my script, other wise it would complain about segmentation faults.
I am running linux though, so maybe windows doesn't care as much.

reumb
03-21-2012, 07:34 AM
gontran,i see your ip on 1 post or not?

Gontran
03-21-2012, 08:47 AM
This script will fail misserably on linux in python 2 or 3.

I started work on something similar about a month ago and I'm surpirsed your script works.
I had to define custom ctype structure & union classes for my script, other wise it would complain about segmentation faults.
I am running linux though, so maybe windows doesn't care as much.

I didn't try at home on linux, but at work, I have no complains running my script on a linux vm (virtualbox, ubuntu) except a dns error returned by librtmp (no segmentation faults). Maybe a network error on my vm config. I will try tonight on my ubuntu laptop and will post my results.

Could you be more precise on the errors you got running my script? We can improve it alltogether.

Gontran
03-21-2012, 08:49 AM
gontran,i see your ip on 1 post or not?

I'm ready to answer to you but I don't understand your question :confused:

amishb
04-27-2012, 09:11 AM
I didn't try at home on linux, but at work, I have no complains running my script on a linux vm (virtualbox, ubuntu) except a dns error returned by librtmp (no segmentation faults). Maybe a network error on my vm config. I will try tonight on my ubuntu laptop and will post my results.

Could you be more precise on the errors you got running my script? We can improve it alltogether.

Hey guys.

I too am also getting the DNS error. I'm running from windows too. Cant figure out what could be causing it. Any ideas? I did a bit of googleing and someone has the same problem when implementing librtmp with VB, and he forgot to initialise winsock. But I'm not really sure what the issue is here.

Thanks

amishb
04-27-2012, 11:11 AM
Fixed it by inserting

import socket

:)

filippo.rosa
03-19-2014, 04:57 AM
This project was definitively abandoned?
I refer to the issue of segmentation fault ...