Repository: streamlink/streamlink
Pull request #641 (2017-03-01 10:55:43)

docs: updated the docs for built-in stream parameters
Added some notes in the docs about settings more complex parameters, lists, dicts, etc.
Made some slight changes to the `StreamURL` class and the associated tests, nothing significant.

See #640; as the documentation is not very clear for setting lists/multiple values. 


Patch:
diff --git a/docs/cli.rst b/docs/cli.rst
index 5cee95cc..46c5f0ed 100644
--- a/docs/cli.rst
+++ b/docs/cli.rst
@@ -284,6 +284,26 @@ Accessing a stream that requires extra parameters to be passed along
 
     $ streamlink "rtmp://streaming.server.net/playpath live=1 swfVfy=http://server.net/flashplayer.swf"
 
+When passing parameters to the built-in stream plugins the values will either be treated as plain
+strings, as is the case in the above example for ``swfVry``, or they will be interpreted as Python literals. For
+example you can pass a Python dict or Python list as one of the parameters.
+
+.. code-block:: console
+
+    $ streamlink "rtmp://streaming.server.net/playpath conn=['B:1', 'S:authMe', 'O:1', 'NN:code:1.23', 'NS:flag:ok', 'O:0']"
+    $ streamlink "hls://streaming.server.net/playpath params={'token': 'magicToken'}"
+
+In the above examples ``conn`` will be passed as the Python list:
+
+.. code-block:: python
+
+    ['B:1', 'S:authMe', 'O:1', 'NN:code:1.23', 'NS:flag:ok', 'O:0']
+
+and ``params`` will be passed as the Python dict:
+
+.. code-block:: python
+
+    {'token': 'magicToken'}
 
 Most streaming technologies simply requires you to pass a HTTP URL, this is
 a Adobe HDS stream:
diff --git a/src/streamlink/plugins/stream.py b/src/streamlink/plugins/stream.py
index 16ff0a62..a87fafa6 100644
--- a/src/streamlink/plugins/stream.py
+++ b/src/streamlink/plugins/stream.py
@@ -25,12 +25,13 @@ SCHEME_REGEX = re.compile(r"^\w+://(.+)")
 
 class StreamURL(Plugin):
     @classmethod
-    def can_handle_url(self, url):
+    def can_handle_url(cls, url):
         parsed = urlparse(url)
 
         return parsed.scheme in PROTOCOL_MAP
 
-    def _parse_params(self, params):
+    @staticmethod
+    def _parse_params(params):
         rval = {}
         matches = re.findall(PARAMS_REGEX, params)
 
@@ -59,7 +60,7 @@ class StreamURL(Plugin):
         if cls != RTMPStream and not SCHEME_REGEX.match(urlnoproto):
             urlnoproto = "http://{0}".format(urlnoproto)
 
-        params = (" ").join(split[1:])
+        params = " ".join(split[1:])
         params = self._parse_params(params)
 
         if cls == RTMPStream:


Test patch:
diff --git a/tests/test_plugin_stream.py b/tests/test_plugin_stream.py
index d55629fe..01a4a97c 100644
--- a/tests/test_plugin_stream.py
+++ b/tests/test_plugin_stream.py
@@ -1,8 +1,7 @@
-import os
 import unittest
 
-from streamlink import Streamlink, PluginError, NoPluginError
-from streamlink.plugins import Plugin
+from streamlink import Streamlink
+from streamlink.plugins.stream import StreamURL
 from streamlink.stream import *
 
 
@@ -56,28 +55,35 @@ class TestPluginStream(unittest.TestCase):
         self.assertEqual(stream.url, url)
         self.assertDictHas(params, stream.args)
 
-    def test_plugin(self):
+    def test_plugin_rtmp(self):
         self._test_rtmp("rtmp://hostname.se/stream",
                         "rtmp://hostname.se/stream", dict())
 
+        self._test_rtmp("rtmp://hostname.se/stream live=1 qarg='a \\'string' noq=test",
+                        "rtmp://hostname.se/stream", dict(live=True, qarg='a \'string', noq="test"))
+
         self._test_rtmp("rtmp://hostname.se/stream live=1 num=47",
                         "rtmp://hostname.se/stream", dict(live=True, num=47))
 
-        self._test_rtmp("rtmp://hostname.se/stream live=1 qarg='a \\'string' noq=test",
-                        "rtmp://hostname.se/stream", dict(live=True, qarg='a \'string', noq="test"))
+        self._test_rtmp("rtmp://hostname.se/stream conn=['B:1','S:authMe','O:1','NN:code:1.23','NS:flag:ok','O:0']",
+                        "rtmp://hostname.se/stream",
+                        dict(conn=['B:1', 'S:authMe', 'O:1', 'NN:code:1.23', 'NS:flag:ok', 'O:0']))
 
+    def test_plugin_hls(self):
         self._test_hls("hls://https://hostname.se/playlist.m3u8",
                        "https://hostname.se/playlist.m3u8")
 
         self._test_hls("hls://hostname.se/playlist.m3u8",
                        "http://hostname.se/playlist.m3u8")
 
+    def test_plugin_akamaihd(self):
         self._test_akamaihd("akamaihd://http://hostname.se/stream",
                             "http://hostname.se/stream")
 
         self._test_akamaihd("akamaihd://hostname.se/stream",
                             "http://hostname.se/stream")
 
+    def test_plugin_http(self):
         self._test_http("httpstream://http://hostname.se/auth.php auth=('test','test2')",
                         "http://hostname.se/auth.php", dict(auth=("test", "test2")))
 
@@ -87,6 +93,19 @@ class TestPluginStream(unittest.TestCase):
         self._test_http("httpstream://https://hostname.se/auth.php verify=False params={'key': 'a value'}",
                         "https://hostname.se/auth.php?key=a+value", dict(verify=False, params=dict(key='a value')))
 
+    def test_parse_params(self):
+        self.assertEqual(
+            dict(verify=False, params=dict(key="a value")),
+            StreamURL._parse_params("""verify=False params={'key': 'a value'}""")
+        )
+        self.assertEqual(
+            dict(verify=False),
+            StreamURL._parse_params("""verify=False""")
+        )
+        self.assertEqual(
+            dict(conn=['B:1', 'S:authMe', 'O:1', 'NN:code:1.23', 'NS:flag:ok', 'O:0']),
+            StreamURL._parse_params(""""conn=['B:1', 'S:authMe', 'O:1', 'NN:code:1.23', 'NS:flag:ok', 'O:0']""")
+        )
 
 if __name__ == "__main__":
     unittest.main()