aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Niall McAndrew <niallm90@gmail.com>2012-08-25 21:30:11 +1200
committerGravatar Niall McAndrew <niallm90@gmail.com>2012-08-25 21:30:11 +1200
commit52636ae1b7bd18e1cd0934289e2ec52e7be84a3d (patch)
treefbebf3587c5737b3a2960678d042b2adf6e5e524
parent8545ee9aec203c0fcbeb64810022a3018e1590d4 (diff)
Added GOMTV plugin support adapted from https://github.com/sjp/GOMstreamer
-rw-r--r--src/livestreamer/cli.py16
-rw-r--r--src/livestreamer/compat.py3
-rw-r--r--src/livestreamer/plugins/__init__.py4
-rw-r--r--src/livestreamer/plugins/gomtv.py202
4 files changed, 222 insertions, 3 deletions
diff --git a/src/livestreamer/cli.py b/src/livestreamer/cli.py
index e04f545..396b8b2 100644
--- a/src/livestreamer/cli.py
+++ b/src/livestreamer/cli.py
@@ -2,6 +2,7 @@ import argparse
import os
import sys
import subprocess
+import getpass
from livestreamer import *
from livestreamer.compat import input, stdout, is_win32
@@ -64,6 +65,13 @@ pluginopt.add_argument("-r", "--rtmpdump", metavar="path",
help="Specify location of rtmpdump")
pluginopt.add_argument("-j", "--jtv-cookie", metavar="cookie",
help="Specify JustinTV cookie to allow access to subscription channels")
+pluginopt.add_argument("--gomtv-cookie", metavar="cookie",
+ help="Specify GOMTV cookie to allow access to streams")
+pluginopt.add_argument("--gomtv-username", metavar="username",
+ help="Specify GOMTV username to allow access to streams")
+pluginopt.add_argument("--gomtv-password", metavar="password",
+ help="Specify GOMTV password to allow access to streams (If left blank you will be prompted)",
+ nargs="?", const=True, default=None)
if is_win32:
RCFILE = os.path.join(os.environ["APPDATA"], "livestreamer", "livestreamerrc")
@@ -257,9 +265,17 @@ def main():
if args.stdout or args.output == "-":
set_msg_output(sys.stderr)
+ if args.gomtv_password is True:
+ gomtv_password = getpass.getpass("GOMTV Password:")
+ else:
+ gomtv_password = args.gomtv_password
+
livestreamer.set_option("errorlog", args.errorlog)
livestreamer.set_option("rtmpdump", args.rtmpdump)
livestreamer.set_plugin_option("justintv", "cookie", args.jtv_cookie)
+ livestreamer.set_plugin_option("gomtv", "cookie", args.gomtv_cookie)
+ livestreamer.set_plugin_option("gomtv", "username", args.gomtv_username)
+ livestreamer.set_plugin_option("gomtv", "password", gomtv_password)
livestreamer.set_loglevel(args.loglevel)
if args.url:
diff --git a/src/livestreamer/compat.py b/src/livestreamer/compat.py
index 5bc1c3c..61711fb 100644
--- a/src/livestreamer/compat.py
+++ b/src/livestreamer/compat.py
@@ -24,9 +24,10 @@ except ImportError:
import urllib2 as urllib
try:
- from urllib.parse import urlparse, parse_qs
+ from urllib.parse import urlparse, parse_qs, urlencode
except ImportError:
from urlparse import urlparse, parse_qs
+ from urllib import urlencode
__all__ = ["is_py2", "is_py3", "is_win32", "input", "stdout", "str",
"bytes", "urllib", "urlparse", "parse_qs"]
diff --git a/src/livestreamer/plugins/__init__.py b/src/livestreamer/plugins/__init__.py
index ad96f6c..b0cdbb5 100644
--- a/src/livestreamer/plugins/__init__.py
+++ b/src/livestreamer/plugins/__init__.py
@@ -34,8 +34,8 @@ class Plugin(object):
to be of highest quality.
"""
- ranking = ["iphonelow", "iphonehigh", "240p", "320k", "360p", "850k",
- "480p", "1400k", "720p", "2400k", "hd", "1080p", "live"]
+ ranking = ["iphonelow", "iphonehigh", "240p", "320k", "360p", "SQTest", "SQ", "850k",
+ "480p", "HQTest", "HQ", "1400k", "720p", "2400k", "hd", "1080p", "live"]
streams = self._get_streams()
for rank in reversed(ranking):
if rank in streams:
diff --git a/src/livestreamer/plugins/gomtv.py b/src/livestreamer/plugins/gomtv.py
new file mode 100644
index 0000000..b2aa494
--- /dev/null
+++ b/src/livestreamer/plugins/gomtv.py
@@ -0,0 +1,202 @@
+'''
+This is derived from https://github.com/sjp/GOMstreamer
+
+and carries the following licence
+
+Copyright 2010 Simon Potter, Tomas Herman
+Copyright 2011 Simon Potter
+Copyright 2011 Fj (fj.mail@gmail.com)
+Copyright 2012 Niall McAndrew (niallm90@gmail.com)
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+'''
+from livestreamer.compat import str, bytes, urlencode
+from livestreamer.plugins import Plugin, PluginError, NoStreamsError
+from livestreamer.stream import HTTPStream
+from livestreamer.utils import urlget, urllib
+from livestreamer.options import Options
+
+import xml.dom.minidom, re
+import cookielib
+import Cookie
+
+class GomTV(Plugin):
+ options = Options({
+ "cookie": None,
+ "username": None,
+ "password": None,
+ })
+
+ @classmethod
+ def can_handle_url(self, url):
+ return "gomtv.net" in url
+
+
+ def _get_streams(self):
+ options = self.options
+ # Setting urllib up so that we can store cookies
+ self.cookiejar = cookielib.LWPCookieJar()
+ self.opener = urllib.build_opener(urllib.HTTPCookieProcessor(self.cookiejar))
+
+ if options.get("cookie"):
+ self.authenticate(cookie=options.get("cookie"))
+ else:
+ self.authenticate(options.get("username"), options.get("password"))
+
+ streams = {}
+ qualities = ["HQ", "SQ", "HQTest", "SQTest"]
+ streamChoice = "both"
+
+ response = self.grabLivePage(self.url)
+
+ goxUrls = []
+ validGoxFound = False
+ failedGoxAll = False
+ for quality in qualities:
+ urls = self.parseHTML(response, quality)
+
+ for url in urls:
+ # Grab the response of the URL listed on the Live page for a stream
+ goxFile = urlget(url, opener=self.opener)
+
+ # The response for the GOX XML if an incorrect stream quality is chosen is 1002.
+ if (goxFile != '1002' and goxFile != ''):
+ streamUrl = self.parseStreamURL(goxFile)
+ req = urllib.Request(streamUrl, headers={"User-Agent": "KPeerClient"})
+ streams[quality] = HTTPStream(self.session, req)
+ validGoxFound = True
+
+ return streams
+
+ def authenticate(self, username=None, password=None, cookie=None):
+ if (username is None or password is None) and cookie is None:
+ raise PluginError("GOMTV.net Requires a username and password or cookie")
+
+
+ if cookie is not None:
+ name,value = cookie.split("=")
+
+ c = cookielib.Cookie(version=0, name=name, value=value,
+ port=None, port_specified=False, domain='gomtv.net',
+ domain_specified=False, domain_initial_dot=False, path='/',
+ path_specified=True, secure=False, expires=None, discard=True,
+ comment=None, comment_url=None, rest={'HttpOnly': None},
+ rfc2109=False)
+ self.cookiejar.set_cookie(c)
+ else:
+ values = {
+ 'cmd': 'login',
+ 'rememberme': '1',
+ 'mb_username': username,
+ 'mb_password': password
+ }
+ data = urlencode(values)
+ # Now expects to log in only via the website. Thanks chrippa.
+ headers = {'Referer': 'http://www.gomtv.net/'}
+ request = urllib.Request('https://ssl.gomtv.net/userinfo/loginProcess.gom', data, headers)
+ urlget(request, opener=self.opener)
+
+ req = urllib.Request('http://www.gomtv.net/forum/list.gom?m=my')
+ if 'Please need login' in urlget(req, opener=self.opener):
+ raise PluginError("Authentication failed")
+
+ # The real response that we want are the cookies, so returning None is fine.
+ return
+
+ def getEventLivePageURL(self, gomtvLiveURL, response):
+ match = re.search(' \"(.*)\";', response)
+ assert match, 'Event Live Page URL not found'
+ return urljoin(gomtvLiveURL, match.group(1))
+
+ def getSeasonURL(self, gomtvURL):
+ # Getting season url from the 'Go Live!' button on the main page.
+ match = re.search('.*liveicon"><a href="([^"]*)"', urlget(gomtvURL, opener=self.opener))
+ assert match, 'golive_btn href not found'
+ return match.group(1)
+
+ def grabLivePage(self, gomtvLiveURL):
+ response = urlget(gomtvLiveURL, opener=self.opener)
+ # If a special event occurs, we know that the live page response
+ # will just be some JavaScript that redirects the browser to the
+ # real live page. We assume that the entireity of this JavaScript
+ # is less than 200 characters long, and that real live pages are
+ # more than that.
+ if len(response) < 200:
+ # Grabbing the real live page URL
+ gomtvLiveURL = self.getEventLivePageURL(gomtvLiveURL, response)
+ response = urlget(gomtvLiveURL, opener=self.opener)
+ return response
+
+ def parseHTML(self, response, quality):
+ urlFromHTML = None
+ # Parsing through the live page for a link to the gox XML file.
+ # Quality is simply passed as a URL parameter e.g. HQ, SQ, SQTest
+ try:
+ patternHTML = r'[^/]+var.+(http://www.gomtv.net/gox[^;]+;)'
+ urlFromHTML = re.search(patternHTML, response).group(1)
+ urlFromHTML = re.sub(r'\" \+ playType \+ \"', quality, urlFromHTML)
+ except AttributeError:
+ pass
+
+ # Finding the title of the stream, probably not necessary but
+ # done for completeness
+ try:
+ patternTitle = r'this\.title[^;]+;'
+ titleFromHTML = re.search(patternTitle, response).group(0)
+ titleFromHTML = re.search(r'\"(.*)\"', titleFromHTML).group(0)
+ titleFromHTML = re.sub(r'"', '', titleFromHTML)
+ urlFromHTML = re.sub(r'"\+ tmpThis.title[^;]+;', titleFromHTML, urlFromHTML)
+ except AttributeError:
+ pass
+
+ # Check for multiple streams going at the same time, and extract the conid and the title
+ # Those streams have the class "live_now"
+ patternLive = r'<a\shref=\"/live/index.gom\?conid=(?P<conid>\d+)\"\sclass=\"live_now\"\stitle=\"(?P<title>[^\"]+)'
+ live_streams = re.findall(patternLive, response)
+
+ if len(live_streams) > 1:
+ liveUrls = []
+ options = range(len(live_streams))
+ for i in options:
+ # Modify the urlFromHTML according to the user
+ singleUrlFromHTML = re.sub(r'conid=\d+', 'conid=' + live_streams[i][0], urlFromHTML)
+ singleTitleHTML = '+'.join(live_streams[i][1].split(' '))
+ singleUrlFromHTML = re.sub(r'title=[\w|.|+]*', 'title=' + singleTitleHTML, singleUrlFromHTML)
+ liveUrls.append(singleUrlFromHTML)
+ return liveUrls
+ else:
+ if urlFromHTML is None:
+ return []
+ else:
+ return [urlFromHTML]
+
+ def parseStreamURL(self, response):
+ # Grabbing the gomcmd URL
+ try:
+ streamPattern = r'<REF href="([^"]*)"\s*/>'
+ regexResult = re.search(streamPattern, response).group(1)
+ except AttributeError:
+ return None
+
+ regexResult = urllib.unquote(regexResult)
+ regexResult = re.sub(r'&amp;', '&', regexResult)
+ # SQ and SQTest streams can be gomp2p links, with actual stream address passed as a parameter.
+ if regexResult.startswith('gomp2p://'):
+ regexResult, n = re.subn(r'^.*LiveAddr=', '', regexResult)
+ # Cosmetics, getting rid of the HTML entity, we don't
+ # need either of the " character or &quot;
+ regexResult = regexResult.replace('&quot;', '')
+ return regexResult
+
+__plugin__ = GomTV