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 -*-
|
2014-04-03 19:52:51 +02:00
|
|
|
import json
|
2022-05-02 12:13:05 +02:00
|
|
|
import logging
|
2019-08-25 00:40:39 +02:00
|
|
|
import re
|
|
|
|
from datetime import datetime
|
|
|
|
from datetime import timedelta
|
2023-06-18 12:46:44 +02:00
|
|
|
from urllib.parse import quote
|
2018-06-24 16:15:55 +02:00
|
|
|
from urllib.parse import urlparse
|
2013-02-12 19:43:37 +01:00
|
|
|
|
2015-09-06 14:19:10 +02:00
|
|
|
from svtplay_dl.error import ServiceError
|
2021-06-04 00:38:49 +02:00
|
|
|
from svtplay_dl.fetcher.dash import dashparse
|
2019-08-25 00:40:39 +02:00
|
|
|
from svtplay_dl.fetcher.hls import hlsparse
|
|
|
|
from svtplay_dl.service import OpenGraphThumbMixin
|
|
|
|
from svtplay_dl.service import Service
|
2021-10-18 16:21:39 +02:00
|
|
|
from svtplay_dl.utils.http import download_thumbnails
|
2013-02-12 19:43:37 +01:00
|
|
|
|
2015-09-15 20:10:32 +02:00
|
|
|
|
2014-01-19 14:26:48 +01:00
|
|
|
class Tv4play(Service, OpenGraphThumbMixin):
|
2019-08-25 00:27:31 +02:00
|
|
|
supported_domains = ["tv4play.se"]
|
2013-01-17 00:21:47 +01:00
|
|
|
|
2015-12-26 11:46:14 +01:00
|
|
|
def get(self):
|
2018-01-15 00:37:18 +01:00
|
|
|
parse = urlparse(self.url)
|
|
|
|
if parse.path[:8] == "/kanaler":
|
2019-09-06 22:49:49 +02:00
|
|
|
end_time_stamp = (datetime.utcnow() - timedelta(minutes=1, seconds=20)).replace(microsecond=0)
|
2018-01-15 22:16:07 +01:00
|
|
|
start_time_stamp = end_time_stamp - timedelta(minutes=1)
|
2018-01-15 00:37:18 +01:00
|
|
|
|
2021-12-18 19:52:08 +01:00
|
|
|
url = (
|
|
|
|
f"https://bbr-l2v.akamaized.net/live/{parse.path[9:]}/master.m3u8?in={start_time_stamp.isoformat()}&out={end_time_stamp.isoformat()}?"
|
2019-08-25 00:27:31 +02:00
|
|
|
)
|
2018-01-15 22:16:07 +01:00
|
|
|
|
2018-05-13 13:06:45 +02:00
|
|
|
self.config.set("live", True)
|
2019-09-06 22:49:49 +02:00
|
|
|
streams = hlsparse(self.config, self.http.request("get", url), url, output=self.output, hls_time_stamp=True)
|
2018-05-08 22:48:55 +02:00
|
|
|
for n in list(streams.keys()):
|
|
|
|
yield streams[n]
|
2018-01-15 00:37:18 +01:00
|
|
|
return
|
|
|
|
|
2023-06-18 12:46:44 +02:00
|
|
|
token = self._login()
|
|
|
|
if token is None:
|
2023-06-18 16:37:19 +02:00
|
|
|
yield ServiceError("You need username / password.")
|
|
|
|
return
|
2023-06-18 12:46:44 +02:00
|
|
|
|
|
|
|
match = self._getjson(self.get_urldata())
|
2018-06-24 16:15:55 +02:00
|
|
|
if not match:
|
|
|
|
yield ServiceError("Can't find json data")
|
2015-01-20 13:10:35 +01:00
|
|
|
return
|
2018-06-24 16:15:55 +02:00
|
|
|
jansson = json.loads(match.group(1))
|
2023-06-18 12:46:44 +02:00
|
|
|
|
|
|
|
if "params" not in jansson["query"]:
|
2019-04-08 23:24:07 +02:00
|
|
|
yield ServiceError("Cant find video id for the video")
|
|
|
|
return
|
|
|
|
|
2023-06-18 12:46:44 +02:00
|
|
|
key_check = None
|
|
|
|
for key in jansson["props"]["apolloStateFromServer"]["ROOT_QUERY"].keys():
|
|
|
|
if key.startswith("media"):
|
|
|
|
key_check = key
|
|
|
|
what = jansson["props"]["apolloStateFromServer"]["ROOT_QUERY"][key_check]["__ref"]
|
|
|
|
|
|
|
|
if what.startswith("Series:"):
|
|
|
|
seriesid = jansson["props"]["apolloStateFromServer"][what]["id"]
|
|
|
|
url = f"https://client-gateway.tv4.a2d.tv/graphql?operationName=suggestedEpisode&variables=%7B%22id%22%3A%22{seriesid}%22%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%2232df600a3e3efb1362bae9ff73a5e7f929e75c154effb38d7b3516c3985e38e0%22%7D%7D"
|
|
|
|
res = self.http.request("get", url, headers={"Client-Name": "tv4-web", "Client-Version": "4.0.0", "Content-Type": "application/json"})
|
|
|
|
vid = res.json()["data"]["series"]["suggestedEpisode"]["episode"]["id"]
|
|
|
|
else:
|
|
|
|
vid = jansson["props"]["apolloStateFromServer"][what]["id"]
|
|
|
|
|
|
|
|
url = f"https://playback2.a2d.tv/play/{vid}?service=tv4play&device=browser&protocol=hls%2Cdash&drm=widevine&browser=GoogleChrome&capabilities=live-drm-adstitch-2%2Cyospace3"
|
|
|
|
res = self.http.request("get", url, headers={"Authorization": f"Bearer {token}"})
|
|
|
|
jansson = res.json()
|
2019-04-08 23:24:07 +02:00
|
|
|
|
2023-06-18 12:46:44 +02:00
|
|
|
item = jansson["metadata"]
|
|
|
|
if item["isDrmProtected"]:
|
2019-09-06 22:49:49 +02:00
|
|
|
yield ServiceError("We can't download DRM protected content from this site.")
|
2019-04-08 23:24:07 +02:00
|
|
|
return
|
|
|
|
|
2023-06-18 12:46:44 +02:00
|
|
|
if item["isLive"]:
|
2019-04-08 23:24:07 +02:00
|
|
|
self.config.set("live", True)
|
2023-06-18 12:46:44 +02:00
|
|
|
if item["seasonNumber"] > 0:
|
|
|
|
self.output["season"] = item["seasonNumber"]
|
|
|
|
if item["episodeNumber"] and item["episodeNumber"] > 0:
|
|
|
|
self.output["episode"] = item["episodeNumber"]
|
|
|
|
self.output["title"] = item["seriesTitle"]
|
2019-04-08 23:24:07 +02:00
|
|
|
self.output["episodename"] = item["title"]
|
|
|
|
self.output["id"] = str(vid)
|
2021-10-18 16:21:39 +02:00
|
|
|
self.output["episodethumbnailurl"] = item["image"]
|
2018-06-24 16:15:55 +02:00
|
|
|
|
|
|
|
if vid is None:
|
|
|
|
yield ServiceError("Cant find video id for the video")
|
2016-03-27 13:02:49 +02:00
|
|
|
return
|
2014-08-27 22:58:37 +02:00
|
|
|
|
2021-06-04 00:38:49 +02:00
|
|
|
if jansson["playbackItem"]["type"] == "hls":
|
2021-05-16 02:22:37 +02:00
|
|
|
yield from hlsparse(
|
2019-08-25 00:27:31 +02:00
|
|
|
self.config,
|
2021-06-04 00:38:49 +02:00
|
|
|
self.http.request("get", jansson["playbackItem"]["manifestUrl"]),
|
|
|
|
jansson["playbackItem"]["manifestUrl"],
|
|
|
|
output=self.output,
|
|
|
|
httpobject=self.http,
|
|
|
|
)
|
|
|
|
yield from dashparse(
|
|
|
|
self.config,
|
|
|
|
self.http.request("get", jansson["playbackItem"]["manifestUrl"].replace(".m3u8", ".mpd")),
|
|
|
|
jansson["playbackItem"]["manifestUrl"].replace(".m3u8", ".mpd"),
|
2019-08-25 00:27:31 +02:00
|
|
|
output=self.output,
|
|
|
|
httpobject=self.http,
|
|
|
|
)
|
2014-08-27 22:58:37 +02:00
|
|
|
|
2023-06-18 12:46:44 +02:00
|
|
|
def _getjson(self, data):
|
|
|
|
match = re.search(r"application\/json\">(.*\})<\/script>", data)
|
2018-06-24 16:15:55 +02:00
|
|
|
return match
|
2016-04-30 14:10:43 +02:00
|
|
|
|
2023-06-18 12:46:44 +02:00
|
|
|
def _login(self):
|
|
|
|
if self.config.get("username") is None or self.config.get("password") is None:
|
|
|
|
return None
|
|
|
|
res = self.http.request(
|
|
|
|
"post",
|
|
|
|
"https://avod-auth-alb.a2d.tv/oauth/authorize",
|
|
|
|
json={
|
|
|
|
"client_id": "tv4-web",
|
|
|
|
"response_type": "token",
|
|
|
|
"credentials": {"username": self.config.get("username"), "password": self.config.get("password")},
|
|
|
|
},
|
|
|
|
)
|
|
|
|
if res.status_code > 400:
|
|
|
|
return None
|
|
|
|
return res.json()["access_token"]
|
|
|
|
|
2018-05-13 13:06:45 +02:00
|
|
|
def find_all_episodes(self, config):
|
2014-04-03 19:52:51 +02:00
|
|
|
episodes = []
|
2018-06-24 16:15:55 +02:00
|
|
|
items = []
|
2023-06-18 12:46:44 +02:00
|
|
|
|
|
|
|
showid, jansson = self._get_seriesid(self.get_urldata(), dict())
|
|
|
|
if showid is None:
|
|
|
|
logging.error("Cant find any videos")
|
|
|
|
return
|
|
|
|
|
|
|
|
for season in jansson["props"]["apolloStateFromServer"][f"Series:{showid}"]["allSeasonLinks"]:
|
|
|
|
graph_list = self._graphql(season["seasonId"])
|
|
|
|
for i in graph_list:
|
|
|
|
if i not in items:
|
|
|
|
items.append(i)
|
|
|
|
|
2022-05-02 12:13:05 +02:00
|
|
|
if config.get("include_clips"):
|
2023-06-18 12:46:44 +02:00
|
|
|
if jansson["props"]["apolloStateFromServer"][f"Series:{showid}"]["hasPanels"]:
|
|
|
|
key_check = None
|
|
|
|
for key in jansson["props"]["apolloStateFromServer"][f"Series:{showid}"].keys():
|
|
|
|
if key.startswith("panels("):
|
|
|
|
key_check = key
|
|
|
|
|
|
|
|
if key_check:
|
|
|
|
for item in jansson["props"]["apolloStateFromServer"][f"Series:{showid}"][key_check]["items"]:
|
|
|
|
if item["__ref"].startswith("Clips"):
|
|
|
|
graph_list = self._graphclips(jansson["props"]["apolloStateFromServer"][item["__ref"]]["id"])
|
|
|
|
for clip in graph_list:
|
|
|
|
episodes.append(f"https://www.tv4play.se/klipp/{clip}")
|
2018-06-24 16:15:55 +02:00
|
|
|
|
|
|
|
items = sorted(items)
|
|
|
|
for item in items:
|
2023-06-18 12:46:44 +02:00
|
|
|
episodes.append(f"https://www.tv4play.se/video/{item}")
|
2018-06-24 16:15:55 +02:00
|
|
|
|
|
|
|
if config.get("all_last") > 0:
|
2019-08-25 00:27:31 +02:00
|
|
|
return episodes[-config.get("all_last") :]
|
2014-12-21 12:27:16 +01:00
|
|
|
return episodes
|
2015-01-20 13:10:35 +01:00
|
|
|
|
2023-06-18 12:46:44 +02:00
|
|
|
def _get_seriesid(self, data, jansson):
|
|
|
|
match = self._getjson(data)
|
|
|
|
if not match:
|
|
|
|
return None, jansson
|
|
|
|
jansson = json.loads(match.group(1))
|
|
|
|
if "params" not in jansson["query"]:
|
|
|
|
return None, jansson
|
|
|
|
showid = jansson["query"]["params"][0]
|
|
|
|
key_check = None
|
|
|
|
for key in jansson["props"]["apolloStateFromServer"]["ROOT_QUERY"].keys():
|
|
|
|
if key.startswith("media"):
|
|
|
|
key_check = key
|
|
|
|
what = jansson["props"]["apolloStateFromServer"]["ROOT_QUERY"][key_check]["__ref"]
|
|
|
|
if what.startswith("Episode"):
|
|
|
|
series = jansson["props"]["apolloStateFromServer"][what]["series"]["__ref"].replace("Series:", "")
|
|
|
|
res = self.http.request("get", f"https://www.tv4play.se/program/{series}/")
|
|
|
|
showid, jansson = self._get_seriesid(res.text, jansson)
|
|
|
|
return showid, jansson
|
|
|
|
|
|
|
|
def _graphql(self, show):
|
2021-02-23 23:45:05 +01:00
|
|
|
items = []
|
2023-06-18 12:46:44 +02:00
|
|
|
nr = 0
|
|
|
|
total = 100
|
|
|
|
while nr <= total:
|
|
|
|
variables = {"seasonId": show, "input": {"limit": 12, "offset": nr}}
|
|
|
|
querystring = f"operationName=SeasonEpisodes&variables={quote(json.dumps(variables, separators=(',', ':')))}&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%22ed1681cdf0f538949697babb57e34e399732046422df6ae60949c693362ca744%22%7D%7D"
|
|
|
|
|
|
|
|
res = self.http.request(
|
|
|
|
"get",
|
|
|
|
f"https://client-gateway.tv4.a2d.tv/graphql?{querystring}",
|
|
|
|
headers={"Client-Name": "tv4-web", "Client-Version": "4.0.0", "Content-Type": "application/json"},
|
|
|
|
)
|
|
|
|
janson = res.json()
|
|
|
|
|
|
|
|
total = janson["data"]["season"]["episodes"]["pageInfo"]["totalCount"]
|
|
|
|
for mediatype in janson["data"]["season"]["episodes"]["items"]:
|
|
|
|
items.append(mediatype["id"])
|
|
|
|
nr += 12
|
|
|
|
return items
|
|
|
|
|
|
|
|
def _graphclips(self, show):
|
|
|
|
items = []
|
|
|
|
nr = 0
|
|
|
|
total = 100
|
|
|
|
while nr <= total:
|
|
|
|
variables = {"panelId": show, "offset": nr, "limit": 8}
|
|
|
|
querystring = f"operationName=Panel&variables={quote(json.dumps(variables, separators=(',', ':')))}&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%22843e9c11ac0512999fecd7646090d2e358c09ef30a4688d948d69dea17b82967%22%7D%7D"
|
|
|
|
res = self.http.request(
|
|
|
|
"get",
|
|
|
|
f"https://client-gateway.tv4.a2d.tv/graphql?{querystring}",
|
|
|
|
headers={"Client-Name": "tv4-web", "Client-Version": "4.0.0", "Content-Type": "application/json"},
|
|
|
|
)
|
|
|
|
janson = res.json()
|
2022-05-02 12:13:05 +02:00
|
|
|
|
2023-06-18 12:46:44 +02:00
|
|
|
total = janson["data"]["panel"]["content"]["pageInfo"]["totalCount"]
|
|
|
|
for mediatype in janson["data"]["panel"]["content"]["items"]:
|
|
|
|
items.append(mediatype["clip"]["id"])
|
|
|
|
nr += 12
|
2021-02-23 23:45:05 +01:00
|
|
|
return items
|
|
|
|
|
2021-10-18 16:21:39 +02:00
|
|
|
def get_thumbnail(self, options):
|
|
|
|
download_thumbnails(self.output, options, [(False, self.output["episodethumbnailurl"])])
|
|
|
|
|
2015-09-15 20:10:32 +02:00
|
|
|
|
2018-06-24 16:15:55 +02:00
|
|
|
class Tv4(Service, OpenGraphThumbMixin):
|
2019-08-25 00:27:31 +02:00
|
|
|
supported_domains = ["tv4.se"]
|
2018-06-24 16:15:55 +02:00
|
|
|
|
|
|
|
def get(self):
|
2021-09-12 23:36:01 +02:00
|
|
|
match = re.search(r"application\/json\"\>(\{.*\})\<\/script", self.get_urldata())
|
2018-06-24 16:15:55 +02:00
|
|
|
if not match:
|
2021-09-12 23:36:01 +02:00
|
|
|
yield ServiceError("Can't find video data'")
|
2018-06-24 16:15:55 +02:00
|
|
|
return
|
2021-09-12 23:36:01 +02:00
|
|
|
janson = json.loads(match.group(1))
|
|
|
|
self.output["id"] = janson["query"]["id"]
|
|
|
|
self.output["title"] = janson["query"]["slug"]
|
|
|
|
if janson["query"]["type"] == "Article":
|
|
|
|
vidasset = janson["props"]["pageProps"]["apolloState"][f"Article:{janson['query']['id']}"]["featured"]["__ref"]
|
|
|
|
self.output["id"] = janson["props"]["pageProps"]["apolloState"][vidasset]["id"]
|
2023-02-17 18:50:03 +01:00
|
|
|
url = f"https://playback2.a2d.tv/play/{self.output['id']}?service=tv4&device=browser&protocol=hls%2Cdash&drm=widevine&capabilities=live-drm-adstitch-2%2Cexpired_assets"
|
2018-06-24 16:15:55 +02:00
|
|
|
res = self.http.request("get", url, cookies=self.cookies)
|
2018-09-23 21:32:02 +02:00
|
|
|
if res.status_code > 200:
|
|
|
|
yield ServiceError("Can't play this because the video is geoblocked.")
|
|
|
|
return
|
2018-06-24 16:15:55 +02:00
|
|
|
if res.json()["playbackItem"]["type"] == "hls":
|
2021-05-16 02:22:37 +02:00
|
|
|
yield from hlsparse(
|
2019-08-25 00:27:31 +02:00
|
|
|
self.config,
|
|
|
|
self.http.request("get", res.json()["playbackItem"]["manifestUrl"]),
|
|
|
|
res.json()["playbackItem"]["manifestUrl"],
|
|
|
|
output=self.output,
|
|
|
|
)
|