diff --git a/resources/lib/twitch/api/usher.py b/resources/lib/twitch/api/usher.py index b03edff..2e66a36 100644 --- a/resources/lib/twitch/api/usher.py +++ b/resources/lib/twitch/api/usher.py @@ -50,14 +50,20 @@ def valid_video_id(video_id): @query def channel_token(channel, platform=keys.WEB, headers={}): data = [{ - "operationName": "PlaybackAccessToken_Template", - "query": "query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: \"web\", playerBackend: \"mediaplayer\", playerType: $playerType}) @include(if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: \"web\", playerBackend: \"mediaplayer\", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}", + "operationName": "PlaybackAccessToken", + "extensions": { + "persistedQuery": { + "version": 1, + "sha256Hash": "ed230aa1e33e07eebb8928504583da78a5173989fadfb1ac94be06a04f3cdbe9" + } + }, "variables": { "isLive": True, "login": channel, "isVod": False, "vodID": "", - "playerType": "site" + "playerType": "embed", + "platform": platform } }] q = GQLQuery('', headers=headers, data=data, use_token=True) @@ -67,14 +73,20 @@ def channel_token(channel, platform=keys.WEB, headers={}): @query def vod_token(video_id, platform=keys.WEB, headers={}): data = [{ - "operationName": "PlaybackAccessToken_Template", - "query": "query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: \"web\", playerBackend: \"mediaplayer\", playerType: $playerType}) @include(if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: \"web\", playerBackend: \"mediaplayer\", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}", + "operationName": "PlaybackAccessToken", + "extensions": { + "persistedQuery": { + "version": 1, + "sha256Hash": "ed230aa1e33e07eebb8928504583da78a5173989fadfb1ac94be06a04f3cdbe9" + } + }, "variables": { "isLive": False, "login": "", "isVod": True, "vodID": video_id, - "playerType": "site" + "playerType": "embed", + "platform": platform } }] q = GQLQuery('', headers=headers, data=data, use_token=True) @@ -88,7 +100,7 @@ def _legacy_video(video_id): return q -def live_request(channel, platform=keys.WEB, headers={}): +def live_request(channel, platform=keys.WEB, headers={}, supported_codecs='av1,h265,h264', low_latency=False): token = channel_token(channel, platform=platform, headers=headers) token = get_access_token(token) @@ -112,6 +124,9 @@ def live_request(channel, platform=keys.WEB, headers={}): q.add_param(keys.PLAYLIST_INCLUDE_FRAMERATE, Boolean.TRUE) q.add_param(keys.RTQOS, keys.CONTROL) q.add_param(keys.PLAYER_BACKEND, keys.MEDIAPLAYER) + q.add_param(keys.SUPPORTED_CODECS, supported_codecs) + if low_latency: + q.add_param(keys.LOW_LATENCY, Boolean.TRUE) url = '?'.join([q.url, urlencode(q.params)]) request_dict = { 'url': url, @@ -122,7 +137,7 @@ def live_request(channel, platform=keys.WEB, headers={}): @query -def _live(channel, token, headers={}): +def _live(channel, token, headers={}, supported_codecs='av1,h265,h264', low_latency=False): signature = token[keys.SIGNATURE] access_token = token[keys.VALUE] @@ -139,11 +154,14 @@ def _live(channel, token, headers={}): q.add_param(keys.PLAYLIST_INCLUDE_FRAMERATE, Boolean.TRUE) q.add_param(keys.RTQOS, keys.CONTROL) q.add_param(keys.PLAYER_BACKEND, keys.MEDIAPLAYER) + q.add_param(keys.SUPPORTED_CODECS, supported_codecs) + if low_latency: + q.add_param(keys.LOW_LATENCY, Boolean.TRUE) return q @m3u8 -def live(channel, platform=keys.WEB, headers={}): +def live(channel, platform=keys.WEB, headers={}, supported_codecs='av1,h265,h264', low_latency=False): token = channel_token(channel, platform=platform, headers=headers) token = get_access_token(token) if not token: @@ -151,10 +169,10 @@ def live(channel, platform=keys.WEB, headers={}): elif isinstance(token, dict) and 'error' in token: return token else: - return _live(channel, token, headers=headers) + return _live(channel, token, headers=headers, supported_codecs=supported_codecs, low_latency=low_latency) -def video_request(video_id, platform=keys.WEB, headers={}): +def video_request(video_id, platform=keys.WEB, headers={}, supported_codecs='av1,h265,h264'): video_id = valid_video_id(video_id) if video_id: token = vod_token(video_id, platform=platform, headers=headers) @@ -181,6 +199,7 @@ def video_request(video_id, platform=keys.WEB, headers={}): q.add_param(keys.BAKING_BREAD, Boolean.TRUE) q.add_param(keys.BAKING_BROWNIES, Boolean.TRUE) q.add_param(keys.BAKING_BROWNIES_TIMEOUT, 1050) + q.add_param(keys.SUPPORTED_CODECS, supported_codecs) url = '?'.join([q.url, urlencode(q.params)]) request_dict = { 'url': url, @@ -193,7 +212,7 @@ def video_request(video_id, platform=keys.WEB, headers={}): @query -def _vod(video_id, token, headers={}): +def _vod(video_id, token, headers={}, supported_codecs='av1,h265,h264'): signature = token[keys.SIGNATURE] access_token = token[keys.VALUE] @@ -211,11 +230,12 @@ def _vod(video_id, token, headers={}): q.add_param(keys.BAKING_BREAD, Boolean.TRUE) q.add_param(keys.BAKING_BROWNIES, Boolean.TRUE) q.add_param(keys.BAKING_BROWNIES_TIMEOUT, 1050) + q.add_param(keys.SUPPORTED_CODECS, supported_codecs) return q @m3u8 -def video(video_id, platform=keys.WEB, headers={}): +def video(video_id, platform=keys.WEB, headers={}, supported_codecs='av1,h265,h264'): video_id = valid_video_id(video_id) if video_id: token = vod_token(video_id, platform=platform, headers=headers) @@ -226,7 +246,7 @@ def video(video_id, platform=keys.WEB, headers={}): elif isinstance(token, dict) and 'error' in token: return token else: - return _vod(video_id, token, headers=headers) + return _vod(video_id, token, headers=headers, supported_codecs=supported_codecs) else: raise NotImplementedError('Unknown Video Type') diff --git a/resources/lib/twitch/keys.py b/resources/lib/twitch/keys.py index c33d0e3..d0cfa10 100644 --- a/resources/lib/twitch/keys.py +++ b/resources/lib/twitch/keys.py @@ -72,6 +72,7 @@ LIVE = 'live' LIVE_ONLY = 'live_only' LOGIN = 'login' +LOW_LATENCY = 'low_latency' MANIFEST_ID = 'manifest_id' MEDIAPLAYER = 'mediaplayer' MESSAGE = 'message' @@ -105,6 +106,7 @@ STREAM_PLAYBACK_ACCESS_TOKEN = 'streamPlaybackAccessToken' STREAM_TYPE = 'stream_type' SUMMARY = 'summary' +SUPPORTED_CODECS = 'supported_codecs' TAG_ID = 'tag_id' TAG_IDS = 'tag_ids' TAG_LIST = 'tag_list' diff --git a/resources/lib/twitch/queries.py b/resources/lib/twitch/queries.py index 63c299e..0be4f7a 100644 --- a/resources/lib/twitch/queries.py +++ b/resources/lib/twitch/queries.py @@ -237,6 +237,7 @@ class GQLQuery(JsonQuery): def __init__(self, path, headers={}, data={}, use_token=True, method=methods.POST): _headers = deepcopy(headers) _headers.setdefault('Client-ID', CLIENT_ID) + _headers.setdefault('Content-Type', 'application/json') if use_token and OAUTH_TOKEN: _headers.setdefault('Authorization', 'OAuth {access_token}'.format(access_token=OAUTH_TOKEN)) super(GQLQuery, self).__init__(_gql_baseurl, _headers, data, method) diff --git a/resources/lib/twitch/scraper.py b/resources/lib/twitch/scraper.py index 433d95d..6867f36 100644 --- a/resources/lib/twitch/scraper.py +++ b/resources/lib/twitch/scraper.py @@ -98,8 +98,10 @@ def download(baseurl, parameters={}, headers={}, data={}, method=methods.GET, re if isinstance(data, list): json_body = data data = None + log.debug('Using json parameter with list data, length: {0}'.format(len(json_body) if json_body else 0)) else: json_body = None + log.debug('Using data parameter, type: {0}, length: {1}'.format(type(data).__name__, len(data) if data else 0)) response = requests.request(method=method, url=url, headers=headers, json=json_body, data=data, verify=SSL_VERIFICATION) content = response.content