aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Christopher Rosell <chrippa@tanuki.se>2012-05-25 17:29:21 +0200
committerGravatar Christopher Rosell <chrippa@tanuki.se>2012-05-25 17:29:21 +0200
commitf2d463ea3f279bb14651b9b517e6721050ae68b2 (patch)
treec263693e24fc1b73d2f42d3f80d6f8a41ba80f58
parent3957b734ed75d8a53f1e195bf9126d8d8625832d (diff)
Add YouTube plugin.
-rw-r--r--README.md1
-rw-r--r--src/livestreamer/plugins/youtube.py94
2 files changed, 95 insertions, 0 deletions
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)