Repository: streamlink/streamlink
Pull request #561 (2017-02-10 14:20:45)

utils.l10n: fix bug in iso3166 country lookup
The `pycountry` vs. `iso639+iso3166` l10n tests were broken so that the only the `pycountry` tests were being run. This has now been fixed, which uncovered a bug in the `iso3166` country lookup.


Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 0256e800..42b0f487 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,34 +1,3 @@
-streamlink 0.3.2 (2017-02-10)
------------------------------
-0.3.2 release of Streamlink!
-
-A minor bug release of 0.3.2 to fix a few issues with stream providers. 
-
-Thanks to all whom have contributed to this (tiny) release!
-
-::
-
-    Charlie Drage <charlie@charliedrage.com> (3):
-          Update release script again to include sdist
-          Fix underlining issue
-          Fix the CHANGELOG.rst
-    
-    Sven <sven@androd.se> (1):
-          Adding Huomao plugin with possibility for different stream qualities.
-    
-    beardypig <beardypig@users.noreply.github.com> (7):
-          Ensure retries with HLS Streams (#522)
-          utils.l10n: add Country/Language classes, use pycountry is the iso modules are not available
-          plugins.crunchyroll: added option to set the session id to a specific value
-          CI: add pycountry for testing
-          plugins.openrectv: add source quality for openrectv
-          utils.l10n: default to en_US when an invalid locale is set
-          stream.hls: pick a better default stream language
-    
-    intact <intact.devel@gmail.com> (1):
-          Add Rtvs.sk Plugin
-    
-
 streamlink 0.3.1 (2017-02-03)
 -----------------------------
 0.3.1 release of Streamlink
diff --git a/appveyor.yml b/appveyor.yml
index ed6353e1..d4c99ebe 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -45,7 +45,7 @@ install:
   # install dev requirements, for testing, etc.
   - "pip install -r dev-requirements.txt"
   - "pip install pycountry" 
-  - "build.cmd %PYTHON%\\python.exe -m pip install ."
+  - "pip install ."
 
 build: off
 
diff --git a/setup.py b/setup.py
index 39d014ee..c5e86b4d 100644
--- a/setup.py
+++ b/setup.py
@@ -46,7 +46,7 @@ srcdir = join(dirname(abspath(__file__)), "src/")
 sys_path.insert(0, srcdir)
 
 setup(name="streamlink",
-      version="0.3.2",
+      version="0.3.1",
       description="Streamlink is command-line utility that extracts streams "
                   "from various services and pipes them into a video player of "
                   "choice.",
diff --git a/src/streamlink/__init__.py b/src/streamlink/__init__.py
index 9750cc17..2fd383f6 100644
--- a/src/streamlink/__init__.py
+++ b/src/streamlink/__init__.py
@@ -12,7 +12,7 @@ Full documentation is available at https://streamlink.github.io.
 
 
 __title__ = "streamlink"
-__version__ = "0.3.2"
+__version__ = "0.3.1"
 __license__ = "Simplified BSD"
 __author__ = "Streamlink"
 __copyright__ = "Copyright 2016 Streamlink"
diff --git a/src/streamlink/plugins/huomao.py b/src/streamlink/plugins/huomao.py
deleted file mode 100644
index ca5371ff..00000000
--- a/src/streamlink/plugins/huomao.py
+++ /dev/null
@@ -1,97 +0,0 @@
-# NOTE: Since a documented API is nowhere to be found for Huomao; this plugin
-# simply extracts the videos stream_id, stream_url and stream_quality by 
-# scraping the HTML and JS of one of Huomaos mobile webpages. 
-
-# When viewing a stream on huomao.com, the base URL references a room_id. This 
-# room_id is mapped one-to-one to a stream_id which references the actual .flv
-# video. Both stream_id, stream_url and stream_quality can be found in the 
-# HTML and JS source of the mobile_page. Since one stream can occur in many
-# different qualities, we scrape all stream_url and stream_quality occurences
-# and return each option to the user.
-
-import re
-
-from streamlink.plugin import Plugin
-from streamlink.plugin.api import http, validate
-from streamlink.stream import HTTPStream
-
-# URL pattern for recognizing inputed Huomao.tv / Huomao.com URL.
-url_re = re.compile(r"""
-    (http(s)?://)? 
-    (www\.)?
-    huomao
-    (\.tv|\.com)
-    /(?P<room_id>\d+)
-""", re.VERBOSE)
-
-# URL used to retrive the stream_id, stream_url and stream_quality based of
-# a room_id.
-mobile_url = "http://www.huomao.com/mobile/mob_live/{0}"
-
-# Pattern for extracting the stream_id from the mobile_url HTML.
-#
-# Example from HTML:
-#   <input id="html_stream" value="efmrCH" type="hidden">
-stream_id_pattern = re.compile(r'id=\"html_stream\" value=\"(?P<stream_id>\w+)\"')
-
-# Pattern for extracting each stream_url, stream_quality_url and a prettified 
-# stream_quality_name used for quality naming.
-# 
-# Example from HTML: 
-#   "2: 'http://live-ws.huomaotv.cn/live/'+stream+'_720/playlist.m3u8'"
-stream_info_pattern = re.compile(r"""
-    [1-9]:
-    \s+
-    '(?P<stream_url>(?:\w|\.|:|-|/)+)
-    '\+stream\+'
-    (?P<stream_quality_url>_?(?P<stream_quality_name>\d*))
-    /playlist.m3u8'
-""", re.VERBOSE)
-
-# Returns the stream_id contained in the HTML.
-def get_stream_id(self, html):
-    stream_id = stream_id_pattern.search(html)
-    
-    if not stream_id:
-        self.logger.error("Failed to extract stream_id.")
-
-    return stream_id.group("stream_id")
-
-# Returns a list of each stream_url, stream_quality_url and stream_quality_name
-# occurence in the JS.
-def get_stream_info(self, html):
-    stream_info = stream_info_pattern.findall(html.text)
-
-    if not stream_info:
-        self.logger.error("Failed to extract stream_info.")
-
-    # Rename the "" quality to "source" by transforming the tuples to a
-    # list and reassigning.
-    stream_info_list = []
-    for info in stream_info:
-        if not info[2]:
-            stream_info_list.append([info[0], info[1], "source"])
-        else:
-            stream_info_list.append(list(info))
-
-    return stream_info_list
-
-class Huomao(Plugin):
-    @classmethod
-    def can_handle_url(self, url):
-        return url_re.match(url)
-
-    def _get_streams(self):
-        room_id = url_re.search(self.url).group("room_id")
-        html = http.get(mobile_url.format(room_id))
-        stream_id = get_stream_id(self, html.text)
-        stream_info = get_stream_info(self, html)
-
-        streams = {}
-        for info in stream_info:
-            streams[info[2]] = HTTPStream(self.session, 
-                info[0] + stream_id + info[1] + ".flv")
-
-        return streams
-
-__plugin__ = Huomao
diff --git a/src/streamlink/utils/l10n.py b/src/streamlink/utils/l10n.py
index 559fa3b7..13ae1f7b 100644
--- a/src/streamlink/utils/l10n.py
+++ b/src/streamlink/utils/l10n.py
@@ -80,6 +80,8 @@ class Language(object):
                             pass
                     if not l:
                         raise KeyError(language)
+                else:
+                    raise KeyError(language)
                 return Language(l.alpha2, l.part3, l.name, l.part2b or l.part2t)
         except (LookupError, KeyError):
             raise LookupError("Invalid language code: {0}".format(language))


Test patch:
diff --git a/tests/test_localization.py b/tests/test_localization.py
index 13f2d5bf..50cb67c7 100644
--- a/tests/test_localization.py
+++ b/tests/test_localization.py
@@ -4,32 +4,37 @@ try:
 except ImportError:
     from mock import patch
 
-
-from streamlink.utils.l10n import Localization, Country, Language
+import streamlink.utils.l10n as l10n
 
 
 class TestLocalization(unittest.TestCase):
+    def setUp(self):
+        l10n.PYCOUNTRY = False
+
+    def test_pycountry(self):
+        self.assertEqual(False, l10n.PYCOUNTRY)
+
     def test_language_code(self):
-        l = Localization("en_US")
+        l = l10n.Localization("en_US")
         self.assertEqual("en_US", l.language_code)
 
     def test_bad_language_code(self):
-        self.assertRaises(LookupError, Localization, "enUS")
+        self.assertRaises(LookupError, l10n.Localization, "enUS")
 
     def test_equivalent(self):
-        l = Localization("en_US")
+        l = l10n.Localization("en_US")
         self.assertTrue(l.equivalent(language="eng"))
         self.assertTrue(l.equivalent(language="en"))
         self.assertTrue(l.equivalent(language="en", country="US"))
         self.assertTrue(l.equivalent(language="en", country="United States"))
 
     def test_equivalent_remap(self):
-        l = Localization("fr_FR")
+        l = l10n.Localization("fr_FR")
         self.assertTrue(l.equivalent(language="fra"))
         self.assertTrue(l.equivalent(language="fre"))
 
     def test_not_equivalent(self):
-        l = Localization("es_ES")
+        l = l10n.Localization("es_ES")
         self.assertFalse(l.equivalent(language="eng"))
         self.assertFalse(l.equivalent(language="en"))
         self.assertFalse(l.equivalent(language="en", country="US"))
@@ -40,48 +45,48 @@ class TestLocalization(unittest.TestCase):
     @patch("locale.getdefaultlocale")
     def test_default(self, getdefaultlocale):
         getdefaultlocale.return_value = (None, None)
-        l = Localization()
+        l = l10n.Localization()
         self.assertEqual("en_US", l.language_code)
         self.assertTrue(l.equivalent(language="en", country="US"))
 
     def test_get_country(self):
         self.assertEqual("US",
-                         Localization.get_country("USA").alpha2)
+                         l10n.Localization.get_country("USA").alpha2)
         self.assertEqual("GB",
-                         Localization.get_country("GB").alpha2)
+                         l10n.Localization.get_country("GB").alpha2)
         self.assertEqual("United States",
-                         Localization.get_country("United States").name)
+                         l10n.Localization.get_country("United States").name)
 
     def test_get_country_miss(self):
-        self.assertRaises(LookupError, Localization.get_country, "XE")
-        self.assertRaises(LookupError, Localization.get_country, "XEX")
-        self.assertRaises(LookupError, Localization.get_country, "Nowhere")
+        self.assertRaises(LookupError, l10n.Localization.get_country, "XE")
+        self.assertRaises(LookupError, l10n.Localization.get_country, "XEX")
+        self.assertRaises(LookupError, l10n.Localization.get_country, "Nowhere")
 
     def test_get_language(self):
         self.assertEqual("eng",
-                         Localization.get_language("en").alpha3)
+                         l10n.Localization.get_language("en").alpha3)
         self.assertEqual("fre",
-                         Localization.get_language("fra").bibliographic)
+                         l10n.Localization.get_language("fra").bibliographic)
         self.assertEqual("fra",
-                         Localization.get_language("fre").alpha3)
+                         l10n.Localization.get_language("fre").alpha3)
         self.assertEqual("gre",
-                         Localization.get_language("gre").bibliographic)
+                         l10n.Localization.get_language("gre").bibliographic)
 
     def test_get_language_miss(self):
-        self.assertRaises(LookupError, Localization.get_language, "00")
-        self.assertRaises(LookupError, Localization.get_language, "000")
-        self.assertRaises(LookupError, Localization.get_language, "0000")
+        self.assertRaises(LookupError, l10n.Localization.get_language, "00")
+        self.assertRaises(LookupError, l10n.Localization.get_language, "000")
+        self.assertRaises(LookupError, l10n.Localization.get_language, "0000")
 
     def test_country_compare(self):
-        a = Country("AA", "AAA", "001", "Test")
-        b = Country("AA", "AAA", "001", "Test")
+        a = l10n.Country("AA", "AAA", "001", "Test")
+        b = l10n.Country("AA", "AAA", "001", "Test")
         self.assertEqual(a, b)
 
     def test_language_compare(self):
-        a = Language("AA", "AAA", "Test")
-        b = Language("AA", None, "Test")
+        a = l10n.Language("AA", "AAA", "Test")
+        b = l10n.Language("AA", None, "Test")
         self.assertEqual(a, b)
 
-        a = Language("BB", "BBB", "Test")
-        b = Language("AA", None, "Test")
+        a = l10n.Language("BB", "BBB", "Test")
+        b = l10n.Language("AA", None, "Test")
         self.assertNotEqual(a, b)
diff --git a/tests/test_localization_pycountry.py b/tests/test_localization_pycountry.py
index e83088ca..0855f576 100644
--- a/tests/test_localization_pycountry.py
+++ b/tests/test_localization_pycountry.py
@@ -1,12 +1,15 @@
 from tests.test_localization import TestLocalization
 import streamlink.utils.l10n as l10n
 
-from pycountry import languages, countries
-l10n.countries = countries
-l10n.languages = languages
-l10n.PYCOUNTRY = True
-
 
 class TestLocalizationPyCountry(TestLocalization):
     """Duplicate of all the Localization tests but using PyCountry instead of the iso* modules"""
-    pass
+
+    def setUp(self):
+        from pycountry import languages, countries
+        l10n.countries = countries
+        l10n.languages = languages
+        l10n.PYCOUNTRY = True
+
+    def test_pycountry(self):
+        self.assertEqual(True, l10n.PYCOUNTRY)