From f2d463ea3f279bb14651b9b517e6721050ae68b2 Mon Sep 17 00:00:00 2001 From: Christopher Rosell Date: Fri, 25 May 2012 17:29:21 +0200 Subject: Add YouTube plugin. --- README.md | 1 + src/livestreamer/plugins/youtube.py | 94 +++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 src/livestreamer/plugins/youtube.py diff --git a/README.md b/README.md index 06fa8e2..cf473a5 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ Currently supported sites are: * Justin.tv/Twitch.tv * Own3D.tv * UStream +* YouTube Justin.tv plugin requires rtmpdump with jtv token support (recent git). diff --git a/src/livestreamer/plugins/youtube.py b/src/livestreamer/plugins/youtube.py new file mode 100644 index 0000000..88ae6de --- /dev/null +++ b/src/livestreamer/plugins/youtube.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 + +from livestreamer.compat import str, bytes, parse_qs +from livestreamer.plugins import Plugin, PluginError, NoStreamsError, register_plugin +from livestreamer.stream import HTTPStream +from livestreamer.utils import urlget + +import re +import json + +class Youtube(Plugin): + @classmethod + def can_handle_url(self, url): + return "youtube.com" in url + + def _get_stream_info(self, url): + data = urlget(url) + config = None + + match = re.search(b"'PLAYER_CONFIG': (.+)\n.+}\);", data) + if match: + config = match.group(1) + + match = re.search(b"yt.playerConfig = (.+)\;\n", data) + if match: + config = match.group(1) + + if config: + try: + parsed = json.loads(str(config, "utf8")) + except ValueError as err: + raise PluginError(("Unable to parse config JSON: {0})").format(err)) + + return parsed + + def _parse_stream_map(self, streammap): + streams = [] + + for stream_qs in streammap.split(","): + stream = parse_qs(stream_qs) + streams.append(stream) + + return streams + + def _parse_format_map(self, formatsmap): + formats = {} + + if len(formatsmap) == 0: + return formats + + for format in formatsmap.split(","): + s = format.split("/") + (w, h) = s[1].split("x") + formats[s[0]] = h + "p" + + return formats + + def _get_streams(self): + info = self._get_stream_info(self.url) + + if not info: + raise NoStreamsError(self.url) + + if "args" in info: + args = info["args"] + else: + raise PluginError("JSON data is missing 'args' key") + + if not "live_playback" in args or args["live_playback"] == "0": + raise NoStreamsError(self.url) + + if not ("url_encoded_fmt_stream_map" in args and "fmt_list" in args): + raise PluginError("JSON data is missing 'url_encoded_fmt_stream_map' or 'fmt_list' keys") + + streams = {} + streammap = self._parse_stream_map(args["url_encoded_fmt_stream_map"]) + formatmap = self._parse_format_map(args["fmt_list"]) + + for streaminfo in streammap: + if not "url" in streaminfo: + continue + + stream = HTTPStream(streaminfo["url"][0]) + + if streaminfo["itag"][0] in formatmap: + quality = formatmap[streaminfo["itag"][0]] + else: + quality = streaminfo["quality"] + + streams[quality] = stream + + return streams + +register_plugin("youtube", Youtube) -- cgit v1.2.3