1
0
mirror of https://github.com/spaam/svtplay-dl.git synced 2024-11-24 12:15:40 +01:00
svtplay-dl/lib/svtplay_dl/service/twitch.py

154 lines
4.9 KiB
Python
Raw Normal View History

2013-03-02 21:26:28 +01:00
# ex:ts=4:sw=4:sts=4:et
# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
# pylint has issues with urlparse: "some types could not be inferred"
# pylint: disable=E1103
from __future__ import absolute_import
import re
2014-02-08 17:09:14 +01:00
import json
2014-06-07 20:43:40 +02:00
import copy
2015-05-23 19:18:04 +02:00
from svtplay_dl.utils.urllib import urlparse, quote_plus
from svtplay_dl.service import Service
2015-05-24 13:59:11 +02:00
from svtplay_dl.utils import get_http_data, filenamify
from svtplay_dl.log import log
2014-04-21 21:55:39 +02:00
from svtplay_dl.fetcher.hls import HLS, hlsparse
2015-05-23 19:18:04 +02:00
class TwitchException(Exception):
2014-02-08 17:09:14 +01:00
pass
2015-05-23 19:18:04 +02:00
class TwitchUrlException(TwitchException):
"""
Used to indicate an invalid URL for a given media_type. E.g.:
TwitchUrlException('video', 'http://twitch.tv/example')
"""
def __init__(self, media_type, url):
super(TwitchUrlException, self).__init__(
"'%s' is not recognized as a %s URL" % (url, media_type)
)
class Twitch(Service):
# Twitch uses language subdomains, e.g. en.www.twitch.tv. They
# are usually two characters, but may have a country suffix as well (e.g.
# zh-tw, zh-cn and pt-br.
supported_domains_re = [
r'^(?:(?:[a-z]{2}-)?[a-z]{2}\.)?(www\.)?twitch\.tv$',
]
2014-02-08 17:09:14 +01:00
api_base_url = 'https://api.twitch.tv'
hls_base_url = 'http://usher.justin.tv/api/channel/hls'
2014-01-06 23:14:06 +01:00
def get(self, options):
urlp = urlparse(self.url)
if self.exclude(options):
return
2015-05-23 19:18:04 +02:00
match = re.match(r'/(\w+)/([bcv])/(\d+)', urlp.path)
if not match:
2015-05-24 12:37:16 +02:00
data = self._get_channel(options, urlp)
2015-05-23 19:18:04 +02:00
else:
if match.group(2) in ["b", "c"]:
log.error("This twitch video type is unsupported")
return
2015-05-24 12:37:16 +02:00
data = self._get_archive(options, match.group(3))
try:
for i in data:
yield i
except TwitchUrlException as e:
2015-05-24 12:37:16 +02:00
log.debug(str(e))
log.error("This twitch video type is unsupported")
return
2015-05-23 19:18:04 +02:00
def _get_static_video(self, options, videoid):
access = self._get_access_token(videoid)
2015-05-24 13:59:11 +02:00
if options.output_auto:
info = json.loads(get_http_data("https://api.twitch.tv/kraken/videos/v%s" % videoid)[1])
options.output = "twitch-%s-%s" % (info["channel"]["name"], filenamify(info["title"]))
2015-05-24 13:59:11 +02:00
2015-05-23 19:18:04 +02:00
if "token" not in access:
raise TwitchUrlException('video', self.url)
2015-05-23 19:18:04 +02:00
nauth = quote_plus(str(access["token"]))
authsig = access["sig"]
2015-05-23 19:18:04 +02:00
url = "http://usher.twitch.tv/vod/%s?nauth=%s&nauthsig=%s" % (
videoid, nauth, authsig)
2015-05-23 19:18:04 +02:00
streams = hlsparse(url)
if streams:
for n in list(streams.keys()):
yield HLS(copy.copy(options), streams[n], n)
2015-05-23 19:18:04 +02:00
def _get_archive(self, options, vid):
try:
for n in self._get_static_video(options, vid):
yield n
except TwitchUrlException as e:
2015-05-23 19:18:04 +02:00
log.error(str(e))
2015-05-23 19:18:04 +02:00
def _get_access_token(self, channel, vtype="vods"):
2014-02-08 17:09:14 +01:00
"""
Get a Twitch access token. It's a three element dict:
* mobile_restricted
* sig
* token
`sig` is a hexadecimal string, and `token` is a JSON blob, with
information about access expiration. `mobile_restricted` is not
important, but is a boolean.
Both `sig` and `token` should be added to the HLS URI, and the
token should, of course, be URI encoded.
"""
2015-05-23 19:18:04 +02:00
return self._ajax_get('/api/%s/%s/access_token' % (vtype, channel))
2014-02-08 17:09:14 +01:00
def _ajax_get(self, method):
url = "%s/%s" % (self.api_base_url, method)
# Logic found in Twitch's global.js. Prepend /kraken/ to url
# path unless the API method already is absolute.
if method[0] != '/':
method = '/kraken/%s' % method
# There are references to a api_token in global.js; it's used
# with the "Twitch-Api-Token" HTTP header. But it doesn't seem
# to be necessary.
2014-12-08 23:07:02 +01:00
error, payload = get_http_data(url, header={
2014-02-08 17:09:14 +01:00
'Accept': 'application/vnd.twitchtv.v2+json'
})
return json.loads(payload)
def _get_hls_url(self, channel):
2015-05-23 19:18:04 +02:00
access = self._get_access_token(channel, "channels")
2014-02-08 17:09:14 +01:00
2015-05-23 19:18:04 +02:00
query = "token=%s&sig=%s" % (quote_plus(access['token']), access['sig'])
2014-02-08 17:09:14 +01:00
return "%s/%s.m3u8?%s" % (self.hls_base_url, channel, query)
2015-05-23 19:18:04 +02:00
def _get_channel(self, options, urlp):
match = re.match(r'/(\w+)', urlp.path)
2014-02-08 17:09:14 +01:00
if not match:
raise TwitchUrlException('channel', urlp.geturl())
2014-02-08 17:09:14 +01:00
channel = match.group(1)
2015-05-24 13:59:11 +02:00
if options.output_auto:
options.output = "twitch-%s" % channel
2014-02-08 17:09:14 +01:00
hls_url = self._get_hls_url(channel)
urlp = urlparse(hls_url)
options.live = True
2014-02-08 17:09:14 +01:00
if not options.output:
options.output = channel
2014-04-21 21:55:39 +02:00
streams = hlsparse(hls_url)
for n in list(streams.keys()):
2014-06-07 20:43:40 +02:00
yield HLS(copy.copy(options), streams[n], n)