mirror of https://github.com/streamlink/streamlink
263 lines
8.4 KiB
Python
263 lines
8.4 KiB
Python
"""
|
|
$description Global live-streaming and video hosting social platform.
|
|
$url vimeo.com
|
|
$type live, vod
|
|
$metadata id
|
|
$metadata author
|
|
$metadata title
|
|
$notes Password protected streams are not supported
|
|
"""
|
|
|
|
import logging
|
|
import re
|
|
from urllib.parse import urljoin, urlparse
|
|
|
|
from streamlink.exceptions import NoStreamsError
|
|
from streamlink.plugin import Plugin, pluginmatcher
|
|
from streamlink.plugin.api import validate
|
|
from streamlink.stream.dash import DASHStream
|
|
from streamlink.stream.ffmpegmux import MuxedStream
|
|
from streamlink.stream.hls import HLSStream
|
|
from streamlink.stream.http import HTTPStream
|
|
from streamlink.utils.url import update_scheme
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
@pluginmatcher(
|
|
name="default",
|
|
pattern=re.compile(r"https?://(?:www\.)?vimeo\.com/(?!event/).+"),
|
|
)
|
|
@pluginmatcher(
|
|
name="event",
|
|
pattern=re.compile(r"https?://(?:www\.)?vimeo\.com/event/(?P<event_id>\d+)"),
|
|
)
|
|
@pluginmatcher(
|
|
name="player",
|
|
pattern=re.compile(r"https?://player\.vimeo\.com/video/\d+"),
|
|
)
|
|
class Vimeo(Plugin):
|
|
VIEWER_URL = "https://vimeo.com/_next/viewer"
|
|
OEMBED_URL = "https://vimeo.com/api/oembed.json"
|
|
EVENT_EMBED_URL = "https://vimeo.com/event/{id}/embed"
|
|
|
|
@staticmethod
|
|
def _schema_config(config):
|
|
schema_cdns = validate.all(
|
|
{
|
|
"cdns": {
|
|
str: validate.all(
|
|
{validate.optional("url"): validate.url()},
|
|
validate.get("url"),
|
|
),
|
|
},
|
|
},
|
|
validate.get("cdns"),
|
|
)
|
|
schema_config = validate.Schema(
|
|
{
|
|
"request": {
|
|
"files": {
|
|
validate.optional("hls"): schema_cdns,
|
|
validate.optional("dash"): schema_cdns,
|
|
validate.optional("progressive"): [
|
|
validate.all(
|
|
{
|
|
validate.optional("url"): validate.url(),
|
|
"quality": str,
|
|
},
|
|
validate.union_get("quality", "url"),
|
|
),
|
|
],
|
|
},
|
|
validate.optional("text_tracks"): [
|
|
validate.all(
|
|
{
|
|
validate.optional("url"): str,
|
|
"lang": str,
|
|
},
|
|
validate.union_get("lang", "url"),
|
|
),
|
|
],
|
|
},
|
|
validate.optional("video"): validate.none_or_all(
|
|
{
|
|
"id": int,
|
|
"title": str,
|
|
"owner": {
|
|
"name": str,
|
|
},
|
|
},
|
|
validate.union_get(
|
|
"id",
|
|
("owner", "name"),
|
|
"title",
|
|
),
|
|
),
|
|
},
|
|
validate.union_get(
|
|
("request", "files", "hls"),
|
|
("request", "files", "dash"),
|
|
("request", "files", "progressive"),
|
|
("request", "text_tracks"),
|
|
"video",
|
|
),
|
|
)
|
|
|
|
return schema_config.validate(config)
|
|
|
|
def _get_dash_url(self, url):
|
|
return self.session.http.get(url, schema=validate.Schema(
|
|
validate.parse_json(),
|
|
{"url": validate.url()},
|
|
validate.get("url"),
|
|
))
|
|
|
|
def _query_player(self):
|
|
return self.session.http.get(self.url, schema=validate.Schema(
|
|
validate.parse_html(),
|
|
validate.xml_xpath_string(".//script[contains(text(),'window.playerConfig')][1]/text()"),
|
|
validate.none_or_all(
|
|
re.compile(r"^\s*window\.playerConfig\s*=\s*(?P<json>{.+?})\s*$"),
|
|
validate.none_or_all(
|
|
validate.get("json"),
|
|
validate.parse_json(),
|
|
validate.transform(self._schema_config),
|
|
),
|
|
),
|
|
))
|
|
|
|
def _get_config_url(self):
|
|
jwt, api_url = self.session.http.get(
|
|
self.VIEWER_URL,
|
|
schema=validate.Schema(
|
|
validate.parse_json(),
|
|
{
|
|
"jwt": str,
|
|
"apiUrl": str,
|
|
},
|
|
validate.union_get("jwt", "apiUrl"),
|
|
),
|
|
)
|
|
uri = self.session.http.get(
|
|
self.OEMBED_URL,
|
|
params={"url": self.url},
|
|
schema=validate.Schema(
|
|
validate.parse_json(),
|
|
{validate.optional("uri"): str},
|
|
validate.get("uri"),
|
|
),
|
|
)
|
|
if not uri:
|
|
return
|
|
|
|
player_config_url = urljoin(update_scheme("https://", api_url), uri)
|
|
config_url = self.session.http.get(
|
|
player_config_url,
|
|
params={"fields": "config_url"},
|
|
headers={"Authorization": f"jwt {jwt}"},
|
|
schema=validate.Schema(
|
|
validate.parse_json(),
|
|
{"config_url": validate.url()},
|
|
validate.get("config_url"),
|
|
),
|
|
)
|
|
|
|
return config_url
|
|
|
|
def _get_config_url_event(self):
|
|
return self.session.http.get(
|
|
self.EVENT_EMBED_URL.format(id=self.match["event_id"]),
|
|
schema=validate.Schema(
|
|
validate.parse_html(),
|
|
validate.xml_xpath_string(".//script[contains(text(),'var htmlString')][1]/text()"),
|
|
validate.none_or_all(
|
|
re.compile(r"var htmlString\s*=\s*`(?P<html>.+?)`;", re.DOTALL),
|
|
validate.none_or_all(
|
|
validate.get("html"),
|
|
validate.parse_html(),
|
|
validate.xml_xpath_string(".//*[@data-config-url][1]/@data-config-url"),
|
|
),
|
|
),
|
|
),
|
|
)
|
|
|
|
def _query_api(self):
|
|
config_url = ""
|
|
if self.matches["event"]:
|
|
log.debug("Getting event config_url")
|
|
config_url = self._get_config_url_event()
|
|
if not config_url:
|
|
log.debug("Getting config_url")
|
|
config_url = self._get_config_url()
|
|
|
|
if not config_url:
|
|
log.error("The content is not available")
|
|
raise NoStreamsError
|
|
|
|
return self.session.http.get(config_url, schema=validate.Schema(
|
|
validate.parse_json(),
|
|
validate.transform(self._schema_config),
|
|
))
|
|
|
|
def _get_streams(self):
|
|
if self.matches["player"]:
|
|
data = self._query_player()
|
|
else:
|
|
data = self._query_api()
|
|
|
|
if not data:
|
|
return
|
|
|
|
hls, dash, progressive, text_tracks, metadata = data
|
|
if metadata:
|
|
self.id, self.author, self.title = metadata
|
|
|
|
streams = []
|
|
|
|
hls = hls or {}
|
|
for url in hls.values():
|
|
if not url:
|
|
continue
|
|
streams.extend(HLSStream.parse_variant_playlist(self.session, url).items())
|
|
break
|
|
|
|
dash = dash or {}
|
|
for url in dash.values():
|
|
if not url:
|
|
continue
|
|
p = urlparse(url)
|
|
if p.path.endswith("dash.mpd"):
|
|
# LIVE
|
|
url = self._get_dash_url(url)
|
|
elif p.path.endswith("master.json"):
|
|
# VOD
|
|
url = url.replace("master.json", "master.mpd")
|
|
else:
|
|
log.error(f"Unsupported DASH path: {p.path}")
|
|
continue
|
|
|
|
streams.extend(DASHStream.parse_manifest(self.session, url).items())
|
|
break
|
|
|
|
streams.extend(
|
|
(quality, HTTPStream(self.session, url))
|
|
for quality, url in progressive or []
|
|
if url
|
|
)
|
|
|
|
if text_tracks and self.session.get_option("mux-subtitles"):
|
|
substreams = {
|
|
lang: HTTPStream(self.session, urljoin("https://vimeo.com/", url))
|
|
for lang, url in text_tracks
|
|
if url
|
|
}
|
|
for quality, stream in streams:
|
|
yield quality, MuxedStream(self.session, stream, subtitles=substreams)
|
|
else:
|
|
yield from streams
|
|
|
|
|
|
__plugin__ = Vimeo
|