1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
#!/usr/bin/env python3
from livestreamer.plugins import Plugin, PluginError, NoStreamsError, register_plugin
from livestreamer.stream import RTMPStream
from livestreamer.utils import swfverify, urlget
from livestreamer.compat import urllib, str
from livestreamer import options
import xml.dom.minidom, re, sys, random
class JustinTV(Plugin):
StreamInfoURL = "http://usher.justin.tv/find/{0}.xml?type=any&p={1}&b_id=true&chansub_guid={2}&private_code=null&group=&channel_subscription={2}"
MetadataURL = "http://www.justin.tv/meta/{0}.xml?on_site=true"
SWFURL = "http://www.justin.tv/widgets/live_embed_player.swf"
@classmethod
def can_handle_url(self, url):
return ("justin.tv" in url) or ("twitch.tv" in url)
def _get_channel_name(self, url):
return url.rstrip("/").rpartition("/")[2]
def _get_metadata(self, channel):
cookie = options.get("jtvcookie")
if cookie:
headers = {"Cookie": cookie}
req = urllib.Request(self.MetadataURL.format(channel), headers=headers)
else:
req = urllib.Request(self.MetadataURL.format(channel))
data = urlget(req)
try:
dom = xml.dom.minidom.parseString(data)
except Exception as err:
raise PluginError(("Unable to parse config XML: {0})").format(err))
meta = dom.getElementsByTagName("meta")[0]
metadata = {}
metadata["title"] = self._get_node_if_exists(dom, "title")
metadata["access_guid"] = self._get_node_if_exists(dom, "access_guid")
return metadata
def _get_node_if_exists(self, dom, name):
elements = dom.getElementsByTagName(name)
if elements and len(elements) > 0:
return self._get_node_text(elements[0])
def _get_node_text(self, element):
res = []
for node in element.childNodes:
if node.nodeType == node.TEXT_NODE:
res.append(node.data)
return "".join(res)
def _get_streaminfo(self, channelname):
def clean_tag(tag):
if tag[0] == "_":
return tag[1:]
else:
return tag
chansub = None
if options.get("jtvcookie"):
metadata = self._get_metadata(channelname)
chansub = metadata["access_guid"]
randomp = int(random.random() * 999999)
url = self.StreamInfoURL.format(channelname, randomp, chansub)
data = urlget(url)
# fix invalid xml
data = re.sub(b"<(\d+)", b"<_\g<1>", data)
data = re.sub(b"</(\d+)", b"</_\g<1>", data)
streams = {}
try:
dom = xml.dom.minidom.parseString(data)
except Exception as err:
raise PluginError(("Unable to parse config XML: {0})").format(err))
nodes = dom.getElementsByTagName("nodes")[0]
swfhash, swfsize = swfverify(self.SWFURL)
for node in nodes.childNodes:
info = {}
for child in node.childNodes:
info[child.tagName] = self._get_node_text(child)
stream = RTMPStream({
"rtmp": ("{0}/{1}").format(info["connect"], info["play"]),
"swfUrl": self.SWFURL,
"swfhash": swfhash,
"swfsize": swfsize,
"live": True
})
if "token" in info:
stream.params["jtv"] = info["token"]
sname = clean_tag(node.tagName)
streams[sname] = stream
return streams
def _get_streams(self):
channelname = self._get_channel_name(self.url)
if not channelname:
raise NoStreamsError(self.url)
return self._get_streaminfo(channelname)
register_plugin("justintv", JustinTV)
|