mirror of
https://github.com/streamlink/streamlink
synced 2024-10-05 01:01:30 +02:00
Added GOMTV plugin support adapted from https://github.com/sjp/GOMstreamer
This commit is contained in:
parent
8545ee9aec
commit
52636ae1b7
@ -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:
|
||||
|
@ -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"]
|
||||
|
@ -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:
|
||||
|
202
src/livestreamer/plugins/gomtv.py
Normal file
202
src/livestreamer/plugins/gomtv.py
Normal file
@ -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'&', '&', 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 "
|
||||
regexResult = regexResult.replace('"', '')
|
||||
return regexResult
|
||||
|
||||
__plugin__ = GomTV
|
Loading…
Reference in New Issue
Block a user