Repository: streamlink/streamlink
Pull request #411 (2017-01-12 19:49:51)

Move streamlink_cli.utils.named_pipe in to streamlink.utils
Refactor of `streamlink_cli.utils.named_pipe` so that the `streamlink` module can remain independent from the `streamlink_cli` module, ie. `streamlink` has no dependency on `streamlink_cli`, only the other way around.

The `streamlink_cli.utils` API is not affected by this change.


Patch:
diff --git a/src/streamlink/plugins/adultswim.py b/src/streamlink/plugins/adultswim.py
index 0286426d..c9e0ff42 100644
--- a/src/streamlink/plugins/adultswim.py
+++ b/src/streamlink/plugins/adultswim.py
@@ -4,10 +4,8 @@ from pprint import pprint
 
 from streamlink.plugin import Plugin
 from streamlink.plugin.api import http, validate
-from streamlink.stream import HDSStream
 from streamlink.stream import HLSStream
-from streamlink.stream import HTTPStream
-from streamlink.utils import parse_json, parse_xml
+from streamlink.utils import parse_json
 
 
 class AdultSwim(Plugin):
@@ -15,55 +13,46 @@ class AdultSwim(Plugin):
         "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) "
         "Chrome/43.0.2357.65 Safari/537.36")
     API_URL = "http://www.adultswim.com/videos/api/v2/videos/{id}?fields=stream"
-    vod_api = " http://www.adultswim.com/videos/api/v0/assets"
 
-    _url_re = re.compile(r"https?://(?:www\.)?adultswim\.com/videos/([^/]*)/?(.*?)/?$")
-    _stream_data_re = re.compile(r"(?:__)?AS_INITIAL_DATA(?:__)? = (\{.*?});", re.M | re.DOTALL)
+    _url_re = re.compile(r"https?://(?:www\.)?adultswim\.com/videos/streams/(.*)")
+    _stream_data_re = re.compile(r".*AS_INITIAL_DATA = (\{.*?});.*", re.M | re.DOTALL)
+
+    _page_data_schema = validate.Schema({
+        u"streams": {
+            validate.text: {
+                u"stream": validate.text
+            }
+        }
+    })
 
-    live_schema = validate.Schema({u"streams": {validate.text: {u"stream": validate.text}}})
-    vod_id_schema = validate.Schema({u"show": {u"sluggedVideo": {u"id": validate.text}}},
-                                    validate.transform(lambda x: x["show"]["sluggedVideo"]["id"]))
     _api_schema = validate.Schema({
         u'status': u'ok',
-        u'data': {u'stream': {
-            u'assets': [{u'url': validate.url()}]
-        }}
+        u'data': {
+            u'stream': {
+                u'assets': [
+                    {
+                        u'url': validate.url()
+                    }
+                ]
+            }
+        }
     })
-    _vod_api_schema = validate.Schema(
-        validate.all(
-            validate.xml_findall(".//files/file"),
-            [validate.xml_element,
-                validate.transform(lambda v: {"bitrate": v.attrib.get("bitrate"), "url": v.text})
-            ]
-        )
-    )
 
     @classmethod
     def can_handle_url(cls, url):
         match = AdultSwim._url_re.match(url)
         return match is not None
 
-    def _get_vod_streams(self, stream_data, show, episode, platform="desktop"):
-        video_id = parse_json(stream_data.group(1), schema=self.vod_id_schema)
-        res = http.get(self.vod_api, params={"platform": platform, "id": video_id})
-        streams = parse_xml(res.text, schema=self._vod_api_schema)
-        urls = set()
-        for stream in streams:
-            bitrate, url = stream["bitrate"], stream["url"]
-            if url not in urls:
-                if url.endswith(".mp4"):
-                    yield "{0}k".format(bitrate), HTTPStream(self.session, url)
-                elif url.endswith(".m3u8"):
-                    for s in HLSStream.parse_variant_playlist(self.session, url).items():
-                        yield s
-                elif url.endswith(".f4m"):
-                    for s in HDSStream.parse_manifest(self.session, url, is_akamai=True).items():
-                        yield s
-                urls.add(stream["url"])
+    def _get_streams(self):
+        # get the page
+        res = http.get(self.url, headers={"User-Agent": self._user_agent})
+        # find the big blob of stream info in the page
+        stream_data = self._stream_data_re.match(res.text)
+        stream_name = AdultSwim._url_re.match(self.url).group(1) or "live-stream"
 
-    def _get_live_stream(self, stream_data, stream_name):
+        if stream_data:
             # parse the stream info as json
-            stream_info = parse_json(stream_data.group(1), schema=self.live_schema)
+            stream_info = parse_json(stream_data.group(1), schema=self._page_data_schema)
             # get the stream ID
             stream_id = stream_info[u"streams"][stream_name][u"stream"]
 
@@ -79,24 +68,6 @@ class AdultSwim(Plugin):
 
             else:
                 self.logger.error("Couldn't find the stream ID for this stream: {}".format(stream_name))
-
-    def _get_streams(self):
-        # get the page
-        url_match = self._url_re.match(self.url)
-        show_name = url_match.group(1)
-        stream_name = url_match.group(2) or "live-stream"
-
-        res = http.get(self.url, headers={"User-Agent": self._user_agent})
-        # find the big blob of stream info in the page
-        stream_data = self._stream_data_re.search(res.text)
-
-        if stream_data:
-            if show_name == "streams":
-                self.logger.debug("Loading LIVE streams for: {0}", stream_name)
-                return self._get_live_stream(stream_data, stream_name)
-            else:
-                self.logger.debug("Loading VOD streams for: {0}/{1}", show_name, stream_name)
-                return self._get_vod_streams(stream_data, show_name, stream_name)
         else:
             self.logger.error("Couldn't find the stream data for this stream: {}".format(stream_name))
 
diff --git a/src/streamlink/stream/ffmpegmux.py b/src/streamlink/stream/ffmpegmux.py
index 73a7961f..76a8be0e 100644
--- a/src/streamlink/stream/ffmpegmux.py
+++ b/src/streamlink/stream/ffmpegmux.py
@@ -8,7 +8,7 @@ import sys
 from streamlink.packages import pbs
 from streamlink.packages.pbs import CommandNotFound
 from streamlink.stream import Stream
-from streamlink_cli.utils import NamedPipe
+from streamlink.utils import NamedPipe
 try:
     from subprocess import DEVNULL
     devnull = lambda: DEVNULL
diff --git a/src/streamlink/stream/hds.py b/src/streamlink/stream/hds.py
index f8ab1330..c7e168ee 100644
--- a/src/streamlink/stream/hds.py
+++ b/src/streamlink/stream/hds.py
@@ -280,8 +280,8 @@ class HDSStreamWorker(SegmentedStreamWorker):
         table = self.segmentruntable.payload.segment_run_entry_table
 
         for segment, start, end in self.iter_segment_table(table):
-            if start - 1 <= fragment <= end:
-                return segment
+            if fragment >= (start + 1) and fragment <= (end + 1):
+                break
         else:
             segment = 1
 
diff --git a/src/streamlink/utils.py b/src/streamlink/utils/__init__.py
similarity index 96%
rename from src/streamlink/utils.py
rename to src/streamlink/utils/__init__.py
index 252af934..ff6e63af 100644
--- a/src/streamlink/utils.py
+++ b/src/streamlink/utils/__init__.py
@@ -7,8 +7,9 @@ try:
 except ImportError:  # pragma: no cover
     import xml.etree.ElementTree as ET
 
-from .compat import urljoin, urlparse, parse_qsl, is_py2
-from .exceptions import PluginError
+from streamlink.compat import urljoin, urlparse, parse_qsl, is_py2
+from streamlink.exceptions import PluginError
+from streamlink.utils.named_pipe import NamedPipe
 
 
 def swfdecompress(data):
@@ -221,4 +222,4 @@ def swfverify(url):  # pragma: no cover
 
 __all__ = ["urlopen", "urlget", "urlresolve", "swfdecompress", "swfverify",
            "verifyjson", "absolute_url", "parse_qsd", "parse_json", "res_json",
-           "parse_xml", "res_xml", "rtmpparse", "prepend_www"]
+           "parse_xml", "res_xml", "rtmpparse", "prepend_www", "NamedPipe"]
diff --git a/src/streamlink/utils/named_pipe.py b/src/streamlink/utils/named_pipe.py
new file mode 100644
index 00000000..c76ae2eb
--- /dev/null
+++ b/src/streamlink/utils/named_pipe.py
@@ -0,0 +1,73 @@
+import os
+import tempfile
+
+from ..compat import is_win32, is_py3
+
+
+if is_win32:
+    from ctypes import windll, cast, c_ulong, c_void_p, byref
+
+    PIPE_ACCESS_OUTBOUND = 0x00000002
+    PIPE_TYPE_BYTE = 0x00000000
+    PIPE_READMODE_BYTE = 0x00000000
+    PIPE_WAIT = 0x00000000
+    PIPE_UNLIMITED_INSTANCES = 255
+    INVALID_HANDLE_VALUE = -1
+
+
+class NamedPipe(object):
+    def __init__(self, name):
+        self.fifo = None
+        self.pipe = None
+
+        if is_win32:
+            self.path = os.path.join("\\\\.\\pipe", name)
+            self.pipe = self._create_named_pipe(self.path)
+        else:
+            self.path = os.path.join(tempfile.gettempdir(), name)
+            self._create_fifo(self.path)
+
+    def _create_fifo(self, name):
+        os.mkfifo(name, 0o660)
+
+    def _create_named_pipe(self, path):
+        bufsize = 8192
+
+        if is_py3:
+            create_named_pipe = windll.kernel32.CreateNamedPipeW
+        else:
+            create_named_pipe = windll.kernel32.CreateNamedPipeA
+
+        pipe = create_named_pipe(path, PIPE_ACCESS_OUTBOUND,
+                                 PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
+                                 PIPE_UNLIMITED_INSTANCES,
+                                 bufsize, bufsize,
+                                 0, None)
+
+        if pipe == INVALID_HANDLE_VALUE:
+            error_code = windll.kernel32.GetLastError()
+            raise IOError("Error code 0x{0:08X}".format(error_code))
+
+        return pipe
+
+    def open(self, mode):
+        if not self.pipe:
+            self.fifo = open(self.path, mode)
+
+    def write(self, data):
+        if self.pipe:
+            windll.kernel32.ConnectNamedPipe(self.pipe, None)
+            written = c_ulong(0)
+            windll.kernel32.WriteFile(self.pipe, cast(data, c_void_p),
+                                      len(data), byref(written),
+                                      None)
+            return written
+        else:
+            return self.fifo.write(data)
+
+    def close(self):
+        if self.pipe:
+            windll.kernel32.DisconnectNamedPipe(self.pipe)
+        else:
+            self.fifo.close()
+            os.unlink(self.path)
diff --git a/src/streamlink_cli/utils/__init__.py b/src/streamlink_cli/utils/__init__.py
index e94bc3bc..9500e017 100644
--- a/src/streamlink_cli/utils/__init__.py
+++ b/src/streamlink_cli/utils/__init__.py
@@ -3,7 +3,7 @@ import json
 from contextlib import contextmanager
 
 from .http_server import HTTPServer
-from .named_pipe import NamedPipe
+from streamlink.utils.named_pipe import NamedPipe
 from .progress import progress
 from .player import find_default_player
 from .stream import stream_to_url
diff --git a/src/streamlink_cli/utils/named_pipe.py b/src/streamlink_cli/utils/named_pipe.py
index c76ae2eb..5da983a3 100644
--- a/src/streamlink_cli/utils/named_pipe.py
+++ b/src/streamlink_cli/utils/named_pipe.py
@@ -1,73 +1,12 @@
-import os
-import tempfile
-
-from ..compat import is_win32, is_py3
-
+# These imports, while unused, are here to provide API compatibility for this module
+from streamlink.utils.named_pipe import NamedPipe
+from ..compat import is_win32
 
 if is_win32:
-    from ctypes import windll, cast, c_ulong, c_void_p, byref
-
-    PIPE_ACCESS_OUTBOUND = 0x00000002
-    PIPE_TYPE_BYTE = 0x00000000
-    PIPE_READMODE_BYTE = 0x00000000
-    PIPE_WAIT = 0x00000000
-    PIPE_UNLIMITED_INSTANCES = 255
-    INVALID_HANDLE_VALUE = -1
-
-
-class NamedPipe(object):
-    def __init__(self, name):
-        self.fifo = None
-        self.pipe = None
-
-        if is_win32:
-            self.path = os.path.join("\\\\.\\pipe", name)
-            self.pipe = self._create_named_pipe(self.path)
-        else:
-            self.path = os.path.join(tempfile.gettempdir(), name)
-            self._create_fifo(self.path)
-
-    def _create_fifo(self, name):
-        os.mkfifo(name, 0o660)
-
-    def _create_named_pipe(self, path):
-        bufsize = 8192
-
-        if is_py3:
-            create_named_pipe = windll.kernel32.CreateNamedPipeW
-        else:
-            create_named_pipe = windll.kernel32.CreateNamedPipeA
-
-        pipe = create_named_pipe(path, PIPE_ACCESS_OUTBOUND,
-                                 PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
-                                 PIPE_UNLIMITED_INSTANCES,
-                                 bufsize, bufsize,
-                                 0, None)
-
-        if pipe == INVALID_HANDLE_VALUE:
-            error_code = windll.kernel32.GetLastError()
-            raise IOError("Error code 0x{0:08X}".format(error_code))
-
-        return pipe
-
-    def open(self, mode):
-        if not self.pipe:
-            self.fifo = open(self.path, mode)
-
-    def write(self, data):
-        if self.pipe:
-            windll.kernel32.ConnectNamedPipe(self.pipe, None)
-            written = c_ulong(0)
-            windll.kernel32.WriteFile(self.pipe, cast(data, c_void_p),
-                                      len(data), byref(written),
-                                      None)
-            return written
-        else:
-            return self.fifo.write(data)
-
-    def close(self):
-        if self.pipe:
-            windll.kernel32.DisconnectNamedPipe(self.pipe)
-        else:
-            self.fifo.close()
-            os.unlink(self.path)
+    from streamlink.utils.named_pipe import (
+        PIPE_ACCESS_OUTBOUND,
+        PIPE_TYPE_BYTE,
+        PIPE_READMODE_BYTE,
+        PIPE_WAIT,
+        PIPE_UNLIMITED_INSTANCES,
+        INVALID_HANDLE_VALUE)


Test patch:
diff --git a/tests/test_plugin_adultswim.py b/tests/test_plugin_adultswim.py
index 202b7995..2fba0fa2 100644
--- a/tests/test_plugin_adultswim.py
+++ b/tests/test_plugin_adultswim.py
@@ -9,8 +9,8 @@ class TestPluginAdultSwim(unittest.TestCase):
         self.assertTrue(AdultSwim.can_handle_url("http://www.adultswim.com/videos/streams/toonami"))
         self.assertTrue(AdultSwim.can_handle_url("http://www.adultswim.com/videos/streams/"))
         self.assertTrue(AdultSwim.can_handle_url("http://www.adultswim.com/videos/streams/last-stream-on-the-left"))
-        self.assertTrue(AdultSwim.can_handle_url("http://www.adultswim.com/videos/specials/the-adult-swim-golf-classic-extended/"))
 
         # shouldn't match
+        self.assertFalse(AdultSwim.can_handle_url("http://www.adultswim.com/videos/specials/the-adult-swim-golf-classic-extended/"))
         self.assertFalse(AdultSwim.can_handle_url("http://www.tvcatchup.com/"))
         self.assertFalse(AdultSwim.can_handle_url("http://www.youtube.com/"))